From 3d746b2d23b7a393e4624e6b2b56e893eb1c450f Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 8 Mar 2024 13:58:38 +0100 Subject: [PATCH 01/69] feat: sctp connection using usrsctp --- webrtc/sctp.nim | 406 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 406 insertions(+) create mode 100644 webrtc/sctp.nim diff --git a/webrtc/sctp.nim b/webrtc/sctp.nim new file mode 100644 index 0000000..91a7cf3 --- /dev/null +++ b/webrtc/sctp.nim @@ -0,0 +1,406 @@ +# 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 tables, bitops, posix, strutils, sequtils +import chronos, chronicles, stew/[ranges/ptr_arith, byteutils, endians2] +import usrsctp +import dtls/dtls +import binary_serialization + +export chronicles + +logScope: + topics = "webrtc sctp" + +# Implementation of an Sctp client and server using the usrsctp library. +# Usrsctp is usable as a single thread but it's not the intended way to +# use it. There's a lot of callbacks calling each other in a synchronous +# way where we want to be able to call asynchronous procedure, but cannot. + +# TODO: +# - Replace doAssert by a proper exception management +# - Find a clean way to manage SCTP ports +# - Unregister address when closing + +proc perror(error: cstring) {.importc, cdecl, header: "".} +proc printf(format: cstring) {.cdecl, importc: "printf", varargs, header: "", gcsafe.} + +type + SctpError* = object of CatchableError + + SctpState = enum + Connecting + Connected + Closed + + SctpMessageParameters* = object + protocolId*: uint32 + streamId*: uint16 + endOfRecord*: bool + unordered*: bool + + SctpMessage* = ref object + data*: seq[byte] + info: sctp_recvv_rn + params*: SctpMessageParameters + + SctpConn* = ref object + conn*: DtlsConn + state: SctpState + connectEvent: AsyncEvent + acceptEvent: AsyncEvent + readLoop: Future[void] + sctp: Sctp + udp: DatagramTransport + address: TransportAddress + sctpSocket: ptr socket + dataRecv: AsyncQueue[SctpMessage] + sentFuture: Future[void] + + Sctp* = ref object + dtls: Dtls + udp: DatagramTransport + connections: Table[TransportAddress, SctpConn] + gotConnection: AsyncEvent + timersHandler: Future[void] + isServer: bool + sockServer: ptr socket + pendingConnections: seq[SctpConn] + pendingConnections2: Table[SockAddr, SctpConn] + sentAddress: TransportAddress + sentFuture: Future[void] + + # These three objects are used for debugging/trace only + SctpChunk = object + chunkType: uint8 + flag: uint8 + length {.bin_value: it.data.len() + 4.}: uint16 + data {.bin_len: it.length - 4.}: seq[byte] + + SctpPacketHeader = object + srcPort: uint16 + dstPort: uint16 + verifTag: uint32 + checksum: uint32 + + SctpPacketStructure = object + header: SctpPacketHeader + chunks: seq[SctpChunk] + +const IPPROTO_SCTP = 132 + +proc getSctpPacket(buffer: seq[byte]): SctpPacketStructure = + # Only used for debugging/trace + result.header = Binary.decode(buffer, SctpPacketHeader) + var size = sizeof(SctpPacketStructure) + while size < buffer.len: + let chunk = Binary.decode(buffer[size..^1], SctpChunk) + result.chunks.add(chunk) + size.inc(chunk.length.int) + while size mod 4 != 0: + # padding; could use `size.inc(-size %% 4)` instead but it lacks clarity + size.inc(1) + +# -- Asynchronous wrapper -- + +template usrsctpAwait(self: SctpConn|Sctp, body: untyped): untyped = + # usrsctpAwait is template which set `sentFuture` to nil then calls (usually) + # an usrsctp function. If during the synchronous run of the usrsctp function + # `sendCallback` is called, then `sentFuture` is set and waited. + self.sentFuture = nil + when type(body) is void: + body + if self.sentFuture != nil: await self.sentFuture + else: + let res = body + if self.sentFuture != nil: await self.sentFuture + res + +# -- SctpConn -- + +proc new(T: typedesc[SctpConn], conn: DtlsConn, sctp: Sctp): T = + T(conn: conn, + sctp: sctp, + state: Connecting, + connectEvent: AsyncEvent(), + acceptEvent: AsyncEvent(), + dataRecv: newAsyncQueue[SctpMessage]() # TODO add some limit for backpressure? + ) + +proc read*(self: SctpConn): Future[SctpMessage] {.async.} = + # Used by DataChannel, returns SctpMessage in order to get the stream + # and protocol ids + return await self.dataRecv.popFirst() + +proc toFlags(params: SctpMessageParameters): uint16 = + if params.endOfRecord: + result = result or SCTP_EOR + if params.unordered: + result = result or SCTP_UNORDERED + +proc write*(self: SctpConn, buf: seq[byte], + sendParams = default(SctpMessageParameters)) {.async.} = + # Used by DataChannel, writes buf on the Dtls connection. + trace "Write", buf + self.sctp.sentAddress = self.address + + var cpy = buf + let sendvErr = + if sendParams == default(SctpMessageParameters): + # If writes is called by DataChannel, sendParams should never + # be the default value. This split is useful for testing. + self.usrsctpAwait: + self.sctpSocket.usrsctp_sendv(cast[pointer](addr cpy[0]), cpy.len().uint, nil, 0, + nil, 0, SCTP_SENDV_NOINFO.cuint, 0) + else: + var sendInfo = sctp_sndinfo( + snd_sid: sendParams.streamId, + # TODO: swapBytes => htonl? + snd_ppid: sendParams.protocolId.swapBytes(), + snd_flags: sendParams.toFlags) + self.usrsctpAwait: + self.sctpSocket.usrsctp_sendv(cast[pointer](addr cpy[0]), cpy.len().uint, nil, 0, + cast[pointer](addr sendInfo), sizeof(sendInfo).SockLen, + SCTP_SENDV_SNDINFO.cuint, 0) + if sendvErr < 0: + # TODO: throw an exception + perror("usrsctp_sendv") + +proc write*(self: SctpConn, s: string) {.async.} = + await self.write(s.toBytes()) + +proc close*(self: SctpConn) {.async.} = + self.usrsctpAwait: + self.sctpSocket.usrsctp_close() + +# -- usrsctp receive data callbacks -- + +proc handleUpcall(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = + # Callback procedure called when we receive data after + # connection has been established. + let + conn = cast[SctpConn](data) + events = usrsctp_get_events(sock) + + trace "Handle Upcall", events + if bitand(events, SCTP_EVENT_READ) != 0: + var + message = SctpMessage( + data: newSeq[byte](4096) + ) + address: Sockaddr_storage + rn: sctp_recvv_rn + addressLen = sizeof(Sockaddr_storage).SockLen + rnLen = sizeof(sctp_recvv_rn).SockLen + infotype: uint + flags: int + let n = sock.usrsctp_recvv(cast[pointer](addr message.data[0]), + message.data.len.uint, + cast[ptr SockAddr](addr address), + cast[ptr SockLen](addr addressLen), + cast[pointer](addr message.info), + cast[ptr SockLen](addr rnLen), + cast[ptr cuint](addr infotype), + cast[ptr cint](addr flags)) + if n < 0: + perror("usrsctp_recvv") + return + elif n > 0: + # It might be necessary to check if infotype == SCTP_RECVV_RCVINFO + message.data.delete(n..= usrsctp_listen(sock, 1) + doAssert 0 == sock.usrsctp_set_upcall(handleAccept, cast[pointer](self)) + self.sockServer = sock + +proc connect*(self: Sctp, + address: TransportAddress, + sctpPort: uint16 = 5000): Future[SctpConn] {.async.} = + let + sctpSocket = usrsctp_socket(AF_CONN, posix.SOCK_STREAM, IPPROTO_SCTP, nil, nil, 0, nil) + conn = SctpConn.new(await self.dtls.connect(address), self) + + trace "Create Connection", address + conn.sctpSocket = sctpSocket + conn.state = Connected + var nodelay: uint32 = 1 + var recvinfo: uint32 = 1 + doAssert 0 == usrsctp_set_non_blocking(conn.sctpSocket, 1) + doAssert 0 == usrsctp_set_upcall(conn.sctpSocket, handleConnect, cast[pointer](conn)) + doAssert 0 == conn.sctpSocket.usrsctp_setsockopt(IPPROTO_SCTP, SCTP_NODELAY, + addr nodelay, sizeof(nodelay).SockLen) + doAssert 0 == conn.sctpSocket.usrsctp_setsockopt(IPPROTO_SCTP, SCTP_RECVRCVINFO, + addr recvinfo, sizeof(recvinfo).SockLen) + var sconn: Sockaddr_conn + sconn.sconn_family = AF_CONN + sconn.sconn_port = htons(sctpPort) + sconn.sconn_addr = cast[pointer](conn) + self.sentAddress = address + usrsctp_register_address(cast[pointer](conn)) + conn.readLoop = conn.readLoopProc() + let connErr = self.usrsctpAwait: + conn.sctpSocket.usrsctp_connect(cast[ptr SockAddr](addr sconn), SockLen(sizeof(sconn))) + doAssert 0 == connErr or errno == posix.EINPROGRESS, ($errno) + conn.state = Connecting + conn.connectEvent.clear() + await conn.connectEvent.wait() + # TODO: check connection state, if closed throw an exception + self.connections[address] = conn + return conn From 654a69ac7ca16492a0c9e739b54f7365d094b8a8 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 8 Mar 2024 14:04:13 +0100 Subject: [PATCH 02/69] feat: added sctp ping&pong examples --- .github/workflows/ci.yml | 4 ++-- examples/ping.nim | 24 ++++++++++++++++++++++++ examples/pong.nim | 30 ++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 examples/ping.nim create mode 100644 examples/pong.nim diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 71e2f03..97c6b36 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,5 +62,5 @@ jobs: nim --version nimble --version # nimble test - # nim c examples/ping.nim - # nim c examples/pong.nim + nim c examples/ping.nim + nim c examples/pong.nim diff --git a/examples/ping.nim b/examples/ping.nim new file mode 100644 index 0000000..ea11c12 --- /dev/null +++ b/examples/ping.nim @@ -0,0 +1,24 @@ +import chronos, stew/byteutils +import ../webrtc/udp_connection +import ../webrtc/stun/stun_connection +import ../webrtc/dtls/dtls +import ../webrtc/sctp + +proc main() {.async.} = + let laddr = initTAddress("127.0.0.1:4244") + let udp = UdpConn() + udp.init(laddr) + let stun = StunConn() + stun.init(udp, laddr) + let dtls = Dtls() + dtls.init(stun, laddr) + let sctp = Sctp() + sctp.init(dtls, laddr) + let conn = await sctp.connect(initTAddress("127.0.0.1:4242"), sctpPort = 13) + while true: + await conn.write("ping".toBytes) + let msg = await conn.read() + echo "Received: ", string.fromBytes(msg.data) + await sleepAsync(1.seconds) + +waitFor(main()) diff --git a/examples/pong.nim b/examples/pong.nim new file mode 100644 index 0000000..b614b59 --- /dev/null +++ b/examples/pong.nim @@ -0,0 +1,30 @@ +import chronos, stew/byteutils +import ../webrtc/udp_connection +import ../webrtc/stun/stun_connection +import ../webrtc/dtls/dtls +import ../webrtc/sctp + +proc sendPong(conn: SctpConn) {.async.} = + var i = 0 + while true: + let msg = await conn.read() + echo "Received: ", string.fromBytes(msg.data) + await conn.write(("pong " & $i).toBytes) + i.inc() + +proc main() {.async.} = + let laddr = initTAddress("127.0.0.1:4242") + let udp = UdpConn() + udp.init(laddr) + let stun = StunConn() + stun.init(udp, laddr) + let dtls = Dtls() + dtls.init(stun, laddr) + let sctp = Sctp() + sctp.init(dtls, laddr) + sctp.listen(13) + while true: + let conn = await sctp.accept() + asyncSpawn conn.sendPong() + +waitFor(main()) From 4b9c506cdc01f225658d389039ee34f4b1106ab7 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Thu, 21 Mar 2024 15:44:27 +0100 Subject: [PATCH 03/69] Unregister address while closing --- webrtc/sctp.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc/sctp.nim b/webrtc/sctp.nim index 91a7cf3..e71e317 100644 --- a/webrtc/sctp.nim +++ b/webrtc/sctp.nim @@ -26,7 +26,6 @@ logScope: # TODO: # - Replace doAssert by a proper exception management # - Find a clean way to manage SCTP ports -# - Unregister address when closing proc perror(error: cstring) {.importc, cdecl, header: "".} proc printf(format: cstring) {.cdecl, importc: "printf", varargs, header: "", gcsafe.} @@ -178,6 +177,7 @@ proc write*(self: SctpConn, s: string) {.async.} = proc close*(self: SctpConn) {.async.} = self.usrsctpAwait: self.sctpSocket.usrsctp_close() + usrsctp_deregister_address(cast[pointer](self)) # -- usrsctp receive data callbacks -- From 9b51d57e508874a8b86c36859dc5c193cd5d486b Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Mon, 15 Apr 2024 16:11:24 +0200 Subject: [PATCH 04/69] change ci building examples --- .github/workflows/ci.yml | 4 +--- webrtc.nimble | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 97c6b36..625d916 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,6 +61,4 @@ jobs: run: | nim --version nimble --version - # nimble test - nim c examples/ping.nim - nim c examples/pong.nim + nimble test diff --git a/webrtc.nimble b/webrtc.nimble index c64ccac..fd3dccb 100644 --- a/webrtc.nimble +++ b/webrtc.nimble @@ -25,6 +25,14 @@ let cfg = import hashes +proc buildExample(filename: string, run = false, extraFlags = "") = + var excstr = nimc & " " & lang & " " & flags & " -p:. " & extraFlags + excstr.add(" examples/" & filename) + exec excstr + if run: + exec "./examples/" & filename.toExe + rmFile "examples/" & filename.toExe + proc runTest(filename: string) = var excstr = nimc & " " & lang & " -d:debug " & cfg & " " & flags excstr.add(" -d:nimOldCaseObjects") # TODO: fix this in binary-serialization @@ -33,5 +41,10 @@ proc runTest(filename: string) = exec excstr & " -r " & " tests/" & filename rmFile "tests/" & filename.toExe -# task test, "Run test": -# runTest("runalltests") +task test, "Runs the test suite": + # runTest("runalltests") + exec "nimble build_example" + +task build_example, "Build the examples": + buildExample("ping") + buildExample("pong") From 9b5c58dd5c53927a5b4c85581d6269d9aa6b490f Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 26 Jul 2024 11:23:20 +0200 Subject: [PATCH 05/69] chore: split files --- webrtc/sctp/sctp_connection.nim | 99 ++++++++++++++++ webrtc/{sctp.nim => sctp/sctp_transport.nim} | 115 +------------------ webrtc/sctp/sctp_utils.nim | 22 ++++ 3 files changed, 127 insertions(+), 109 deletions(-) create mode 100644 webrtc/sctp/sctp_connection.nim rename webrtc/{sctp.nim => sctp/sctp_transport.nim} (75%) create mode 100644 webrtc/sctp/sctp_utils.nim diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim new file mode 100644 index 0000000..924be41 --- /dev/null +++ b/webrtc/sctp/sctp_connection.nim @@ -0,0 +1,99 @@ +# 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 usrsctp +import ./sctp_utils +import ../errors + +logScope: + topics = "webrtc sctp_connection" + +proc sctpStrerror(error: cint): cstring {.importc: "strerror", cdecl, header: "".} + +type + SctpState = enum + Connecting + Connected + Closed + + SctpMessageParameters* = object + protocolId*: uint32 + streamId*: uint16 + endOfRecord*: bool + unordered*: bool + + SctpMessage* = ref object + data*: seq[byte] + info: sctp_recvv_rn + params*: SctpMessageParameters + + SctpConn* = ref object + conn*: DtlsConn + state: SctpState + connectEvent: AsyncEvent + acceptEvent: AsyncEvent + readLoop: Future[void] + udp: DatagramTransport + address: TransportAddress + sctpSocket: ptr socket + dataRecv: AsyncQueue[SctpMessage] + sentFuture: Future[void] + +proc new(T: typedesc[SctpConn], conn: DtlsConn): T = + T(conn: conn, + state: Connecting, + connectEvent: AsyncEvent(), + acceptEvent: AsyncEvent(), + dataRecv: newAsyncQueue[SctpMessage]() + ) + +proc read*(self: SctpConn): Future[SctpMessage] {.async.} = + # Used by DataChannel, returns SctpMessage in order to get the stream + # and protocol ids + return await self.dataRecv.popFirst() + +proc toFlags(params: SctpMessageParameters): uint16 = + if params.endOfRecord: + result = result or SCTP_EOR + if params.unordered: + result = result or SCTP_UNORDERED + +proc write*(self: SctpConn, buf: seq[byte], + sendParams = default(SctpMessageParameters)) {.async.} = + # Used by DataChannel, writes buf on the Dtls connection. + trace "Write", buf + + var cpy = buf + let sendvErr = + if sendParams == default(SctpMessageParameters): + # If writes is called by DataChannel, sendParams should never + # be the default value. This split is useful for testing. + self.usrsctpAwait: + self.sctpSocket.usrsctp_sendv(cast[pointer](addr cpy[0]), cpy.len().uint, nil, 0, + nil, 0, SCTP_SENDV_NOINFO.cuint, 0) + else: + var sendInfo = sctp_sndinfo( + snd_sid: sendParams.streamId, + snd_ppid: sendParams.protocolId.swapBytes(), + snd_flags: sendParams.toFlags) + self.usrsctpAwait: + self.sctpSocket.usrsctp_sendv(cast[pointer](addr cpy[0]), cpy.len().uint, nil, 0, + cast[pointer](addr sendInfo), sizeof(sendInfo).SockLen, + SCTP_SENDV_SNDINFO.cuint, 0) + if sendvErr < 0: + raise newException(WebRtcError, $(sctpStrerror(sendvErr))) + +proc write*(self: SctpConn, s: string) {.async.} = + await self.write(s.toBytes()) + +proc close*(self: SctpConn) {.async.} = + self.usrsctpAwait: + self.sctpSocket.usrsctp_close() + usrsctp_deregister_address(cast[pointer](self)) diff --git a/webrtc/sctp.nim b/webrtc/sctp/sctp_transport.nim similarity index 75% rename from webrtc/sctp.nim rename to webrtc/sctp/sctp_transport.nim index e71e317..bf5f89b 100644 --- a/webrtc/sctp.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -10,7 +10,8 @@ import tables, bitops, posix, strutils, sequtils import chronos, chronicles, stew/[ranges/ptr_arith, byteutils, endians2] import usrsctp -import dtls/dtls +import ../dtls/dtls_transport +import ./sctp_connection import binary_serialization export chronicles @@ -31,39 +32,9 @@ proc perror(error: cstring) {.importc, cdecl, header: "".} proc printf(format: cstring) {.cdecl, importc: "printf", varargs, header: "", gcsafe.} type - SctpError* = object of CatchableError - - SctpState = enum - Connecting - Connected - Closed - - SctpMessageParameters* = object - protocolId*: uint32 - streamId*: uint16 - endOfRecord*: bool - unordered*: bool - - SctpMessage* = ref object - data*: seq[byte] - info: sctp_recvv_rn - params*: SctpMessageParameters - - SctpConn* = ref object - conn*: DtlsConn - state: SctpState - connectEvent: AsyncEvent - acceptEvent: AsyncEvent - readLoop: Future[void] - sctp: Sctp - udp: DatagramTransport - address: TransportAddress - sctpSocket: ptr socket - dataRecv: AsyncQueue[SctpMessage] - sentFuture: Future[void] - Sctp* = ref object dtls: Dtls + laddr*: TransportAddress udp: DatagramTransport connections: Table[TransportAddress, SctpConn] gotConnection: AsyncEvent @@ -72,7 +43,6 @@ type sockServer: ptr socket pendingConnections: seq[SctpConn] pendingConnections2: Table[SockAddr, SctpConn] - sentAddress: TransportAddress sentFuture: Future[void] # These three objects are used for debugging/trace only @@ -106,79 +76,6 @@ proc getSctpPacket(buffer: seq[byte]): SctpPacketStructure = # padding; could use `size.inc(-size %% 4)` instead but it lacks clarity size.inc(1) -# -- Asynchronous wrapper -- - -template usrsctpAwait(self: SctpConn|Sctp, body: untyped): untyped = - # usrsctpAwait is template which set `sentFuture` to nil then calls (usually) - # an usrsctp function. If during the synchronous run of the usrsctp function - # `sendCallback` is called, then `sentFuture` is set and waited. - self.sentFuture = nil - when type(body) is void: - body - if self.sentFuture != nil: await self.sentFuture - else: - let res = body - if self.sentFuture != nil: await self.sentFuture - res - -# -- SctpConn -- - -proc new(T: typedesc[SctpConn], conn: DtlsConn, sctp: Sctp): T = - T(conn: conn, - sctp: sctp, - state: Connecting, - connectEvent: AsyncEvent(), - acceptEvent: AsyncEvent(), - dataRecv: newAsyncQueue[SctpMessage]() # TODO add some limit for backpressure? - ) - -proc read*(self: SctpConn): Future[SctpMessage] {.async.} = - # Used by DataChannel, returns SctpMessage in order to get the stream - # and protocol ids - return await self.dataRecv.popFirst() - -proc toFlags(params: SctpMessageParameters): uint16 = - if params.endOfRecord: - result = result or SCTP_EOR - if params.unordered: - result = result or SCTP_UNORDERED - -proc write*(self: SctpConn, buf: seq[byte], - sendParams = default(SctpMessageParameters)) {.async.} = - # Used by DataChannel, writes buf on the Dtls connection. - trace "Write", buf - self.sctp.sentAddress = self.address - - var cpy = buf - let sendvErr = - if sendParams == default(SctpMessageParameters): - # If writes is called by DataChannel, sendParams should never - # be the default value. This split is useful for testing. - self.usrsctpAwait: - self.sctpSocket.usrsctp_sendv(cast[pointer](addr cpy[0]), cpy.len().uint, nil, 0, - nil, 0, SCTP_SENDV_NOINFO.cuint, 0) - else: - var sendInfo = sctp_sndinfo( - snd_sid: sendParams.streamId, - # TODO: swapBytes => htonl? - snd_ppid: sendParams.protocolId.swapBytes(), - snd_flags: sendParams.toFlags) - self.usrsctpAwait: - self.sctpSocket.usrsctp_sendv(cast[pointer](addr cpy[0]), cpy.len().uint, nil, 0, - cast[pointer](addr sendInfo), sizeof(sendInfo).SockLen, - SCTP_SENDV_SNDINFO.cuint, 0) - if sendvErr < 0: - # TODO: throw an exception - perror("usrsctp_sendv") - -proc write*(self: SctpConn, s: string) {.async.} = - await self.write(s.toBytes()) - -proc close*(self: SctpConn) {.async.} = - self.usrsctpAwait: - self.sctpSocket.usrsctp_close() - usrsctp_deregister_address(cast[pointer](self)) - # -- usrsctp receive data callbacks -- proc handleUpcall(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = @@ -313,12 +210,13 @@ proc stopServer*(self: Sctp) = pc.sctpSocket.usrsctp_close() self.sockServer.usrsctp_close() -proc init*(self: Sctp, dtls: Dtls, laddr: TransportAddress) = +proc new*(T: type Sctp, dtls: Dtls) = self.gotConnection = newAsyncEvent() self.timersHandler = timersHandler() self.dtls = dtls - usrsctp_init_nothreads(laddr.port.uint16, sendCallback, printf) + + usrsctp_init_nothreads(dtls.laddr.port.uint16, sendCallback, printf) discard usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_NONE) discard usrsctp_sysctl_set_sctp_ecn_enable(1) usrsctp_register_address(cast[pointer](self)) @@ -392,7 +290,6 @@ proc connect*(self: Sctp, sconn.sconn_family = AF_CONN sconn.sconn_port = htons(sctpPort) sconn.sconn_addr = cast[pointer](conn) - self.sentAddress = address usrsctp_register_address(cast[pointer](conn)) conn.readLoop = conn.readLoopProc() let connErr = self.usrsctpAwait: diff --git a/webrtc/sctp/sctp_utils.nim b/webrtc/sctp/sctp_utils.nim new file mode 100644 index 0000000..1e9a9c5 --- /dev/null +++ b/webrtc/sctp/sctp_utils.nim @@ -0,0 +1,22 @@ +# 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. + +template usrsctpAwait*(self: untyped, body: untyped): untyped = + # usrsctpAwait is template which set `sentFuture` to nil then calls (usually) + # an usrsctp function. If during the synchronous run of the usrsctp function + # `sendCallback` is called, then `sentFuture` is set and waited. + # self should be Sctp or SctpConn + self.sentFuture = nil + when type(body) is void: + (body) + if self.sentFuture != nil: await self.sentFuture + else: + let res = (body) + if self.sentFuture != nil: await self.sentFuture + res From 3aa14e99256a48cc630f7cf117356f713af594af Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 26 Jul 2024 11:44:44 +0200 Subject: [PATCH 06/69] chore: expose what should be exposed & removed artifact object fields --- webrtc/sctp/sctp_connection.nim | 29 +++++++++++++++-------------- webrtc/sctp/sctp_transport.nim | 20 ++++++++++---------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 924be41..2b76c85 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -7,18 +7,20 @@ # This file may not be copied, modified, or distributed except according to # those terms. -import chronos, chronicles +import posix +import chronos, chronicles, stew/[endians2, byteutils] import usrsctp import ./sctp_utils import ../errors +import ../dtls/dtls_connection logScope: topics = "webrtc sctp_connection" -proc sctpStrerror(error: cint): cstring {.importc: "strerror", cdecl, header: "".} +proc sctpStrerror(error: int): cstring {.importc: "strerror", cdecl, header: "".} type - SctpState = enum + SctpState* = enum Connecting Connected Closed @@ -31,22 +33,21 @@ type SctpMessage* = ref object data*: seq[byte] - info: sctp_recvv_rn + info*: sctp_recvv_rn params*: SctpMessageParameters SctpConn* = ref object conn*: DtlsConn - state: SctpState - connectEvent: AsyncEvent - acceptEvent: AsyncEvent - readLoop: Future[void] - udp: DatagramTransport - address: TransportAddress - sctpSocket: ptr socket - dataRecv: AsyncQueue[SctpMessage] - sentFuture: Future[void] + state*: SctpState + connectEvent*: AsyncEvent + acceptEvent*: AsyncEvent + readLoop*: Future[void] + address*: TransportAddress + sctpSocket*: ptr socket + dataRecv*: AsyncQueue[SctpMessage] + sentFuture*: Future[void] -proc new(T: typedesc[SctpConn], conn: DtlsConn): T = +proc new*(T: typedesc[SctpConn], conn: DtlsConn): T = T(conn: conn, state: Connecting, connectEvent: AsyncEvent(), diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index bf5f89b..e72ca61 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -8,10 +8,11 @@ # those terms. import tables, bitops, posix, strutils, sequtils -import chronos, chronicles, stew/[ranges/ptr_arith, byteutils, endians2] +import chronos, chronicles, stew/[ranges/ptr_arith, endians2] import usrsctp -import ../dtls/dtls_transport -import ./sctp_connection +import ../errors +import ../dtls/[dtls_transport, dtls_connection] +import ./[sctp_connection, sctp_utils] import binary_serialization export chronicles @@ -35,7 +36,6 @@ type Sctp* = ref object dtls: Dtls laddr*: TransportAddress - udp: DatagramTransport connections: Table[TransportAddress, SctpConn] gotConnection: AsyncEvent timersHandler: Future[void] @@ -210,21 +210,21 @@ proc stopServer*(self: Sctp) = pc.sctpSocket.usrsctp_close() self.sockServer.usrsctp_close() -proc new*(T: type Sctp, dtls: Dtls) = +proc new*(T: type Sctp, dtls: Dtls): T = + var self = T() self.gotConnection = newAsyncEvent() self.timersHandler = timersHandler() self.dtls = dtls - usrsctp_init_nothreads(dtls.laddr.port.uint16, sendCallback, printf) discard usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_NONE) discard usrsctp_sysctl_set_sctp_ecn_enable(1) usrsctp_register_address(cast[pointer](self)) + return self proc stop*(self: Sctp) {.async.} = # TODO: close every connections discard self.usrsctpAwait usrsctp_finish() - self.udp.close() proc readLoopProc(res: SctpConn) {.async.} = while true: @@ -239,8 +239,8 @@ proc readLoopProc(res: SctpConn) {.async.} = proc accept*(self: Sctp): Future[SctpConn] {.async.} = if not self.isServer: - raise newException(SctpError, "Not a server") - var res = SctpConn.new(await self.dtls.accept(), self) + raise newException(WebRtcError, "SCTP - Not a server") + var res = SctpConn.new(await self.dtls.accept()) usrsctp_register_address(cast[pointer](res)) res.readLoop = res.readLoopProc() res.acceptEvent.clear() @@ -273,7 +273,7 @@ proc connect*(self: Sctp, sctpPort: uint16 = 5000): Future[SctpConn] {.async.} = let sctpSocket = usrsctp_socket(AF_CONN, posix.SOCK_STREAM, IPPROTO_SCTP, nil, nil, 0, nil) - conn = SctpConn.new(await self.dtls.connect(address), self) + conn = SctpConn.new(await self.dtls.connect(address)) trace "Create Connection", address conn.sctpSocket = sctpSocket From d09aa309a9fcda7679226a5a368a0ae6b8f58096 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 26 Jul 2024 12:27:25 +0200 Subject: [PATCH 07/69] fix: examples --- examples/ping.nim | 21 +++++++++------------ examples/pong.nim | 21 +++++++++------------ 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/examples/ping.nim b/examples/ping.nim index ea11c12..bee1971 100644 --- a/examples/ping.nim +++ b/examples/ping.nim @@ -1,19 +1,16 @@ import chronos, stew/byteutils -import ../webrtc/udp_connection -import ../webrtc/stun/stun_connection -import ../webrtc/dtls/dtls -import ../webrtc/sctp +import ../webrtc/udp_transport +import ../webrtc/stun/stun_transport +import ../webrtc/dtls/dtls_transport +import ../webrtc/sctp/[sctp_transport, sctp_connection] proc main() {.async.} = let laddr = initTAddress("127.0.0.1:4244") - let udp = UdpConn() - udp.init(laddr) - let stun = StunConn() - stun.init(udp, laddr) - let dtls = Dtls() - dtls.init(stun, laddr) - let sctp = Sctp() - sctp.init(dtls, laddr) + let udp = UdpTransport.new(laddr) + let stun = Stun.new(udp) + let dtls = Dtls.new(stun) + let sctp = Sctp.new(dtls) + let conn = await sctp.connect(initTAddress("127.0.0.1:4242"), sctpPort = 13) while true: await conn.write("ping".toBytes) diff --git a/examples/pong.nim b/examples/pong.nim index b614b59..79c018c 100644 --- a/examples/pong.nim +++ b/examples/pong.nim @@ -1,8 +1,8 @@ import chronos, stew/byteutils -import ../webrtc/udp_connection -import ../webrtc/stun/stun_connection -import ../webrtc/dtls/dtls -import ../webrtc/sctp +import ../webrtc/udp_transport +import ../webrtc/stun/stun_transport +import ../webrtc/dtls/dtls_transport +import ../webrtc/sctp/[sctp_transport, sctp_connection] proc sendPong(conn: SctpConn) {.async.} = var i = 0 @@ -14,14 +14,11 @@ proc sendPong(conn: SctpConn) {.async.} = proc main() {.async.} = let laddr = initTAddress("127.0.0.1:4242") - let udp = UdpConn() - udp.init(laddr) - let stun = StunConn() - stun.init(udp, laddr) - let dtls = Dtls() - dtls.init(stun, laddr) - let sctp = Sctp() - sctp.init(dtls, laddr) + let udp = UdpTransport.new(laddr) + let stun = Stun.new(udp) + let dtls = Dtls.new(stun) + let sctp = Sctp.new(dtls) + sctp.listen(13) while true: let conn = await sctp.accept() From ccf671c1544f5e7d08a781b08e0a21b4d7a68df9 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 26 Jul 2024 12:28:29 +0200 Subject: [PATCH 08/69] chore: shuffle part of the code to its correct file --- webrtc/sctp/sctp_connection.nim | 36 +++++++++++++++++++----- webrtc/sctp/sctp_transport.nim | 50 --------------------------------- webrtc/sctp/sctp_utils.nim | 30 ++++++++++++++++++++ 3 files changed, 59 insertions(+), 57 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 2b76c85..cf5ba15 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -8,7 +8,7 @@ # those terms. import posix -import chronos, chronicles, stew/[endians2, byteutils] +import chronos, chronicles, stew/[ptrops, endians2, byteutils] import usrsctp import ./sctp_utils import ../errors @@ -47,6 +47,34 @@ type dataRecv*: AsyncQueue[SctpMessage] sentFuture*: Future[void] +# -- usrsctp send data callback -- + +proc sendCallback*(ctx: pointer, + buffer: pointer, + length: uint, + tos: uint8, + set_df: uint8): cint {.cdecl.} = + # This proc is called by usrsctp everytime usrsctp tries to send data. + let data = usrsctp_dumppacket(buffer, length, SCTP_DUMP_OUTBOUND) + if data != nil: + trace "sendCallback", sctpPacket = data.getSctpPacket(), length + usrsctp_freedumpbuffer(data) + let sctpConn = cast[SctpConn](ctx) + let buf = @(buffer.makeOpenArray(byte, int(length))) + proc testSend() {.async.} = + try: + trace "Send To", address = sctpConn.address + await sctpConn.conn.write(buf) + except CatchableError as exc: + trace "Send Failed", message = exc.msg + sctpConn.sentFuture = testSend() + +proc toFlags(params: SctpMessageParameters): uint16 = + if params.endOfRecord: + result = result or SCTP_EOR + if params.unordered: + result = result or SCTP_UNORDERED + proc new*(T: typedesc[SctpConn], conn: DtlsConn): T = T(conn: conn, state: Connecting, @@ -60,12 +88,6 @@ proc read*(self: SctpConn): Future[SctpMessage] {.async.} = # and protocol ids return await self.dataRecv.popFirst() -proc toFlags(params: SctpMessageParameters): uint16 = - if params.endOfRecord: - result = result or SCTP_EOR - if params.unordered: - result = result or SCTP_UNORDERED - proc write*(self: SctpConn, buf: seq[byte], sendParams = default(SctpMessageParameters)) {.async.} = # Used by DataChannel, writes buf on the Dtls connection. diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index e72ca61..4b4af60 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -45,37 +45,8 @@ type pendingConnections2: Table[SockAddr, SctpConn] sentFuture: Future[void] - # These three objects are used for debugging/trace only - SctpChunk = object - chunkType: uint8 - flag: uint8 - length {.bin_value: it.data.len() + 4.}: uint16 - data {.bin_len: it.length - 4.}: seq[byte] - - SctpPacketHeader = object - srcPort: uint16 - dstPort: uint16 - verifTag: uint32 - checksum: uint32 - - SctpPacketStructure = object - header: SctpPacketHeader - chunks: seq[SctpChunk] - const IPPROTO_SCTP = 132 -proc getSctpPacket(buffer: seq[byte]): SctpPacketStructure = - # Only used for debugging/trace - result.header = Binary.decode(buffer, SctpPacketHeader) - var size = sizeof(SctpPacketStructure) - while size < buffer.len: - let chunk = Binary.decode(buffer[size..^1], SctpChunk) - result.chunks.add(chunk) - size.inc(chunk.length.int) - while size mod 4 != 0: - # padding; could use `size.inc(-size %% 4)` instead but it lacks clarity - size.inc(1) - # -- usrsctp receive data callbacks -- proc handleUpcall(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = @@ -171,27 +142,6 @@ proc handleConnect(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = else: warn "should be connecting", currentState = conn.state -# -- usrsctp send data callback -- - -proc sendCallback(ctx: pointer, - buffer: pointer, - length: uint, - tos: uint8, - set_df: uint8): cint {.cdecl.} = - let data = usrsctp_dumppacket(buffer, length, SCTP_DUMP_OUTBOUND) - if data != nil: - trace "sendCallback", sctpPacket = data.getSctpPacket(), length - usrsctp_freedumpbuffer(data) - let sctpConn = cast[SctpConn](ctx) - let buf = @(buffer.makeOpenArray(byte, int(length))) - proc testSend() {.async.} = - try: - trace "Send To", address = sctpConn.address - await sctpConn.conn.write(buf) - except CatchableError as exc: - trace "Send Failed", message = exc.msg - sctpConn.sentFuture = testSend() - # -- Sctp -- proc timersHandler() {.async.} = diff --git a/webrtc/sctp/sctp_utils.nim b/webrtc/sctp/sctp_utils.nim index 1e9a9c5..2410a07 100644 --- a/webrtc/sctp/sctp_utils.nim +++ b/webrtc/sctp/sctp_utils.nim @@ -7,6 +7,36 @@ # This file may not be copied, modified, or distributed except according to # those terms. +import binary_serialization + +type + # These three objects are used for debugging/trace only + SctpChunk* = object + chunkType*: uint8 + flag*: uint8 + length* {.bin_value: it.data.len() + 4.}: uint16 + data* {.bin_len: it.length - 4.}: seq[byte] + SctpPacketHeader* = object + srcPort*: uint16 + dstPort*: uint16 + verifTag*: uint32 + checksum*: uint32 + SctpPacketStructure* = object + header*: SctpPacketHeader + chunks*: seq[SctpChunk] + +proc getSctpPacket*(buffer: seq[byte]): SctpPacketStructure = + # Only used for debugging/trace + result.header = Binary.decode(buffer, SctpPacketHeader) + var size = sizeof(SctpPacketStructure) + while size < buffer.len: + let chunk = Binary.decode(buffer[size..^1], SctpChunk) + result.chunks.add(chunk) + size.inc(chunk.length.int) + while size mod 4 != 0: + # padding; could use `size.inc(-size %% 4)` instead but it lacks clarity + size.inc(1) + template usrsctpAwait*(self: untyped, body: untyped): untyped = # usrsctpAwait is template which set `sentFuture` to nil then calls (usually) # an usrsctp function. If during the synchronous run of the usrsctp function From 3f118953f93c01756007390b6de499128ade07f7 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 26 Jul 2024 13:04:33 +0200 Subject: [PATCH 09/69] chore: rename handleUpcall into recvCallback --- webrtc/sctp/sctp_connection.nim | 56 ++++++++++++++++++++++++++++++-- webrtc/sctp/sctp_transport.nim | 57 ++------------------------------- 2 files changed, 57 insertions(+), 56 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index cf5ba15..442ae83 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -7,7 +7,7 @@ # This file may not be copied, modified, or distributed except according to # those terms. -import posix +import posix, bitops import chronos, chronicles, stew/[ptrops, endians2, byteutils] import usrsctp import ./sctp_utils @@ -17,6 +17,7 @@ import ../dtls/dtls_connection logScope: topics = "webrtc sctp_connection" +# TODO: closing connection if usrsctp_recv / usrsctp_read fails proc sctpStrerror(error: int): cstring {.importc: "strerror", cdecl, header: "".} type @@ -47,7 +48,58 @@ type dataRecv*: AsyncQueue[SctpMessage] sentFuture*: Future[void] -# -- usrsctp send data callback -- +# -- usrsctp send and receive callback -- + +proc recvCallback*(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = + # Callback procedure called when we receive data after + # connection has been established. + let + conn = cast[SctpConn](data) + events = usrsctp_get_events(sock) + + trace "Handle Upcall", events + if bitand(events, SCTP_EVENT_READ) != 0: + var + message = SctpMessage( + data: newSeq[byte](4096) + ) + address: Sockaddr_storage + rn: sctp_recvv_rn + addressLen = sizeof(Sockaddr_storage).SockLen + rnLen = sizeof(sctp_recvv_rn).SockLen + infotype: uint + flags: int + let n = sock.usrsctp_recvv(cast[pointer](addr message.data[0]), + message.data.len.uint, + cast[ptr SockAddr](addr address), + cast[ptr SockLen](addr addressLen), + cast[pointer](addr message.info), + cast[ptr SockLen](addr rnLen), + cast[ptr cuint](addr infotype), + cast[ptr cint](addr flags)) + if n < 0: + trace "usrsctp_recvv", error = sctpStrerror(n) + # TODO: should close + return + elif n > 0: + # It might be necessary to check if infotype == SCTP_RECVV_RCVINFO + message.data.delete(n..".} proc printf(format: cstring) {.cdecl, importc: "printf", varargs, header: "", gcsafe.} type @@ -49,56 +48,6 @@ const IPPROTO_SCTP = 132 # -- usrsctp receive data callbacks -- -proc handleUpcall(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = - # Callback procedure called when we receive data after - # connection has been established. - let - conn = cast[SctpConn](data) - events = usrsctp_get_events(sock) - - trace "Handle Upcall", events - if bitand(events, SCTP_EVENT_READ) != 0: - var - message = SctpMessage( - data: newSeq[byte](4096) - ) - address: Sockaddr_storage - rn: sctp_recvv_rn - addressLen = sizeof(Sockaddr_storage).SockLen - rnLen = sizeof(sctp_recvv_rn).SockLen - infotype: uint - flags: int - let n = sock.usrsctp_recvv(cast[pointer](addr message.data[0]), - message.data.len.uint, - cast[ptr SockAddr](addr address), - cast[ptr SockLen](addr addressLen), - cast[pointer](addr message.info), - cast[ptr SockLen](addr rnLen), - cast[ptr cuint](addr infotype), - cast[ptr cint](addr flags)) - if n < 0: - perror("usrsctp_recvv") - return - elif n > 0: - # It might be necessary to check if infotype == SCTP_RECVV_RCVINFO - message.data.delete(n.. Date: Fri, 26 Jul 2024 13:11:23 +0200 Subject: [PATCH 10/69] chore: remove import & change variable name --- webrtc/sctp/sctp_connection.nim | 2 +- webrtc/sctp/sctp_transport.nim | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 442ae83..4e969bd 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -7,7 +7,7 @@ # This file may not be copied, modified, or distributed except according to # those terms. -import posix, bitops +import posix, bitops, sequtils import chronos, chronicles, stew/[ptrops, endians2, byteutils] import usrsctp import ./sctp_utils diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 52a1137..1101d9f 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -7,8 +7,8 @@ # This file may not be copied, modified, or distributed except according to # those terms. -import tables, posix, strutils, sequtils -import chronos, chronicles, stew/[ranges/ptr_arith, endians2] +import tables, bitops, posix, strutils +import chronos, chronicles import usrsctp import ../errors import ../dtls/[dtls_transport, dtls_connection] @@ -139,12 +139,12 @@ proc readLoopProc(res: SctpConn) {.async.} = proc accept*(self: Sctp): Future[SctpConn] {.async.} = if not self.isServer: raise newException(WebRtcError, "SCTP - Not a server") - var res = SctpConn.new(await self.dtls.accept()) - usrsctp_register_address(cast[pointer](res)) - res.readLoop = res.readLoopProc() - res.acceptEvent.clear() - await res.acceptEvent.wait() - return res + var conn = SctpConn.new(await self.dtls.accept()) + usrsctp_register_address(cast[pointer](conn)) + conn.readLoop = conn.readLoopProc() + conn.acceptEvent.clear() + await conn.acceptEvent.wait() + return conn proc listen*(self: Sctp, sctpPort: uint16 = 5000) = if self.isServer: From d6f73ce55daf1571f6fbee415bc5392e29582b46 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 26 Jul 2024 13:33:49 +0200 Subject: [PATCH 11/69] feat: check if socket is nil after accepting --- webrtc/sctp/sctp_connection.nim | 10 +++----- webrtc/sctp/sctp_transport.nim | 45 ++++++++++++++++++--------------- webrtc/sctp/sctp_utils.nim | 2 ++ 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 4e969bd..293a478 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -18,13 +18,11 @@ logScope: topics = "webrtc sctp_connection" # TODO: closing connection if usrsctp_recv / usrsctp_read fails -proc sctpStrerror(error: int): cstring {.importc: "strerror", cdecl, header: "".} - type SctpState* = enum - Connecting - Connected - Closed + SctpConnecting + SctpConnected + SctpClosed SctpMessageParameters* = object protocolId*: uint32 @@ -129,7 +127,7 @@ proc toFlags(params: SctpMessageParameters): uint16 = proc new*(T: typedesc[SctpConn], conn: DtlsConn): T = T(conn: conn, - state: Connecting, + state: SctpConnecting, connectEvent: AsyncEvent(), acceptEvent: AsyncEvent(), dataRecv: newAsyncQueue[SctpMessage]() diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 1101d9f..2031f41 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -46,46 +46,40 @@ type const IPPROTO_SCTP = 132 -# -- usrsctp receive data callbacks -- +# -- usrsctp accept and connect callbacks -- proc handleAccept(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = - # Callback procedure called when accepting a connection. + # Callback procedure called when accepting a connection trace "Handle Accept" var sconn: Sockaddr_conn slen: Socklen = sizeof(Sockaddr_conn).uint32 let sctp = cast[Sctp](data) - # TODO: check if sctpSocket != nil sctpSocket = usrsctp_accept(sctp.sockServer, cast[ptr SockAddr](addr sconn), addr slen) + conn = cast[SctpConn](sconn.sconn_addr) - let conn = cast[SctpConn](sconn.sconn_addr) - conn.sctpSocket = sctpSocket - conn.state = Connected - var nodelay: uint32 = 1 - var recvinfo: uint32 = 1 - doAssert 0 == sctpSocket.usrsctp_set_non_blocking(1) - doAssert 0 == conn.sctpSocket.usrsctp_set_upcall(recvCallback, cast[pointer](conn)) - doAssert 0 == conn.sctpSocket.usrsctp_setsockopt(IPPROTO_SCTP, SCTP_NODELAY, - addr nodelay, sizeof(nodelay).SockLen) - doAssert 0 == conn.sctpSocket.usrsctp_setsockopt(IPPROTO_SCTP, SCTP_RECVRCVINFO, - addr recvinfo, sizeof(recvinfo).SockLen) + if sctpSocket.isNil(): + warn "usrsctp_accept fails", error = sctpStrerror(errno) + conn.state = SctpClosed + else: + conn.sctpSocket = sctpSocket + conn.state = SctpConnected conn.acceptEvent.fire() proc handleConnect(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = # Callback procedure called when connecting - trace "Handle Connect" let conn = cast[SctpConn](data) events = usrsctp_get_events(sock) - trace "Handle Upcall", events, state = conn.state - if conn.state == Connecting: + trace "Handle Connect", events, state = conn.state + if conn.state == SctpConnecting: if bitand(events, SCTP_EVENT_ERROR) != 0: warn "Cannot connect", address = conn.address - conn.state = Closed + conn.state = SctpClosed elif bitand(events, SCTP_EVENT_WRITE) != 0: - conn.state = Connected + conn.state = SctpConnected doAssert 0 == usrsctp_set_upcall(conn.sctpSocket, recvCallback, data) conn.connectEvent.fire() else: @@ -144,6 +138,15 @@ proc accept*(self: Sctp): Future[SctpConn] {.async.} = conn.readLoop = conn.readLoopProc() conn.acceptEvent.clear() await conn.acceptEvent.wait() + + var nodelay: uint32 = 1 + var recvinfo: uint32 = 1 + doAssert 0 == conn.sctpSocket.usrsctp_set_non_blocking(1) + doAssert 0 == conn.sctpSocket.usrsctp_set_upcall(recvCallback, cast[pointer](conn)) + doAssert 0 == conn.sctpSocket.usrsctp_setsockopt(IPPROTO_SCTP, SCTP_NODELAY, + addr nodelay, sizeof(nodelay).SockLen) + doAssert 0 == conn.sctpSocket.usrsctp_setsockopt(IPPROTO_SCTP, SCTP_RECVRCVINFO, + addr recvinfo, sizeof(recvinfo).SockLen) return conn proc listen*(self: Sctp, sctpPort: uint16 = 5000) = @@ -176,7 +179,7 @@ proc connect*(self: Sctp, trace "Create Connection", address conn.sctpSocket = sctpSocket - conn.state = Connected + conn.state = SctpConnected var nodelay: uint32 = 1 var recvinfo: uint32 = 1 doAssert 0 == usrsctp_set_non_blocking(conn.sctpSocket, 1) @@ -194,7 +197,7 @@ proc connect*(self: Sctp, let connErr = self.usrsctpAwait: conn.sctpSocket.usrsctp_connect(cast[ptr SockAddr](addr sconn), SockLen(sizeof(sconn))) doAssert 0 == connErr or errno == posix.EINPROGRESS, ($errno) - conn.state = Connecting + conn.state = SctpConnecting conn.connectEvent.clear() await conn.connectEvent.wait() # TODO: check connection state, if closed throw an exception diff --git a/webrtc/sctp/sctp_utils.nim b/webrtc/sctp/sctp_utils.nim index 2410a07..57c31ed 100644 --- a/webrtc/sctp/sctp_utils.nim +++ b/webrtc/sctp/sctp_utils.nim @@ -9,6 +9,8 @@ import binary_serialization +proc sctpStrerror*(error: int): cstring {.importc: "strerror", cdecl, header: "".} + type # These three objects are used for debugging/trace only SctpChunk* = object From 8910d1ee544b3c044d2348da1fb3a57a390f493d Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 26 Jul 2024 13:34:13 +0200 Subject: [PATCH 12/69] chore: renaming address into raddr --- webrtc/sctp/sctp_connection.nim | 3 ++- webrtc/sctp/sctp_transport.nim | 11 ++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 293a478..fc75ed8 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -41,7 +41,7 @@ type connectEvent*: AsyncEvent acceptEvent*: AsyncEvent readLoop*: Future[void] - address*: TransportAddress + raddr*: TransportAddress sctpSocket*: ptr socket dataRecv*: AsyncQueue[SctpMessage] sentFuture*: Future[void] @@ -130,6 +130,7 @@ proc new*(T: typedesc[SctpConn], conn: DtlsConn): T = state: SctpConnecting, connectEvent: AsyncEvent(), acceptEvent: AsyncEvent(), + raddr: conn.raddr, dataRecv: newAsyncQueue[SctpMessage]() ) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 2031f41..15cfdce 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -76,7 +76,7 @@ proc handleConnect(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = trace "Handle Connect", events, state = conn.state if conn.state == SctpConnecting: if bitand(events, SCTP_EVENT_ERROR) != 0: - warn "Cannot connect", address = conn.address + warn "Cannot connect", raddr = conn.raddr conn.state = SctpClosed elif bitand(events, SCTP_EVENT_WRITE) != 0: conn.state = SctpConnected @@ -147,6 +147,7 @@ proc accept*(self: Sctp): Future[SctpConn] {.async.} = addr nodelay, sizeof(nodelay).SockLen) doAssert 0 == conn.sctpSocket.usrsctp_setsockopt(IPPROTO_SCTP, SCTP_RECVRCVINFO, addr recvinfo, sizeof(recvinfo).SockLen) + self.connections[conn.raddr] = conn return conn proc listen*(self: Sctp, sctpPort: uint16 = 5000) = @@ -171,13 +172,13 @@ proc listen*(self: Sctp, sctpPort: uint16 = 5000) = self.sockServer = sock proc connect*(self: Sctp, - address: TransportAddress, + raddr: TransportAddress, sctpPort: uint16 = 5000): Future[SctpConn] {.async.} = let sctpSocket = usrsctp_socket(AF_CONN, posix.SOCK_STREAM, IPPROTO_SCTP, nil, nil, 0, nil) - conn = SctpConn.new(await self.dtls.connect(address)) + conn = SctpConn.new(await self.dtls.connect(raddr)) - trace "Create Connection", address + trace "Create Connection", raddr conn.sctpSocket = sctpSocket conn.state = SctpConnected var nodelay: uint32 = 1 @@ -201,5 +202,5 @@ proc connect*(self: Sctp, conn.connectEvent.clear() await conn.connectEvent.wait() # TODO: check connection state, if closed throw an exception - self.connections[address] = conn + self.connections[raddr] = conn return conn From 73e47316d317de338967c93f0866e8dbe0c796b4 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 26 Jul 2024 14:39:07 +0200 Subject: [PATCH 13/69] chore: rewrite connect / accept to manage error while calling usrsctp fonctions --- webrtc/sctp/sctp_transport.nim | 97 +++++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 32 deletions(-) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 15cfdce..729ccc6 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -13,7 +13,6 @@ import usrsctp import ../errors import ../dtls/[dtls_transport, dtls_connection] import ./[sctp_connection, sctp_utils] -import binary_serialization export chronicles @@ -130,23 +129,61 @@ proc readLoopProc(res: SctpConn) {.async.} = usrsctp_freedumpbuffer(data) usrsctp_conninput(cast[pointer](res), unsafeAddr msg[0], uint(msg.len), 0) +proc socketSetup( + conn: SctpConn, + callback: proc (a1: ptr socket, a2: pointer, a3: cint) {.cdecl.} +): bool = + var + errorCode = conn.sctpSocket.usrsctp_set_non_blocking(1) + nodelay: uint32 = 1 + recvinfo: uint32 = 1 + + if errorCode != 0: + warn "usrsctp_set_non_blocking fails", error = sctpStrerror(errorCode) + return false + + errorCode = conn.sctpSocket.usrsctp_set_upcall(callback, cast[pointer](conn)) + if errorCode != 0: + warn "usrsctp_set_upcall fails", error = sctpStrerror(errorCode) + return false + + errorCode = conn.sctpSocket.usrsctp_setsockopt( + IPPROTO_SCTP, + SCTP_NODELAY, + addr nodelay, + sizeof(nodelay).SockLen, + ) + if errorCode != 0: + warn "usrsctp_setsockopt nodelay fails", error = sctpStrerror(errorCode) + return false + + errorCode = conn.sctpSocket.usrsctp_setsockopt( + IPPROTO_SCTP, + SCTP_RECVRCVINFO, + addr recvinfo, + sizeof(recvinfo).SockLen, + ) + if errorCode != 0: + warn "usrsctp_setsockopt recvinfo fails", error = sctpStrerror(errorCode) + return false + return true + proc accept*(self: Sctp): Future[SctpConn] {.async.} = + ## Accept an Sctp Connection + ## if not self.isServer: raise newException(WebRtcError, "SCTP - Not a server") - var conn = SctpConn.new(await self.dtls.accept()) - usrsctp_register_address(cast[pointer](conn)) - conn.readLoop = conn.readLoopProc() - conn.acceptEvent.clear() - await conn.acceptEvent.wait() - - var nodelay: uint32 = 1 - var recvinfo: uint32 = 1 - doAssert 0 == conn.sctpSocket.usrsctp_set_non_blocking(1) - doAssert 0 == conn.sctpSocket.usrsctp_set_upcall(recvCallback, cast[pointer](conn)) - doAssert 0 == conn.sctpSocket.usrsctp_setsockopt(IPPROTO_SCTP, SCTP_NODELAY, - addr nodelay, sizeof(nodelay).SockLen) - doAssert 0 == conn.sctpSocket.usrsctp_setsockopt(IPPROTO_SCTP, SCTP_RECVRCVINFO, - addr recvinfo, sizeof(recvinfo).SockLen) + var conn: SctpConn + while true: + conn = SctpConn.new(await self.dtls.accept()) + usrsctp_register_address(cast[pointer](conn)) + conn.readLoop = conn.readLoopProc() + conn.acceptEvent.clear() + await conn.acceptEvent.wait() + if conn.state == SctpConnected or conn.socketSetup(recvCallback): + break + await conn.close() + self.connections[conn.raddr] = conn return conn @@ -174,33 +211,29 @@ proc listen*(self: Sctp, sctpPort: uint16 = 5000) = proc connect*(self: Sctp, raddr: TransportAddress, sctpPort: uint16 = 5000): Future[SctpConn] {.async.} = - let - sctpSocket = usrsctp_socket(AF_CONN, posix.SOCK_STREAM, IPPROTO_SCTP, nil, nil, 0, nil) - conn = SctpConn.new(await self.dtls.connect(raddr)) - trace "Create Connection", raddr - conn.sctpSocket = sctpSocket - conn.state = SctpConnected - var nodelay: uint32 = 1 - var recvinfo: uint32 = 1 - doAssert 0 == usrsctp_set_non_blocking(conn.sctpSocket, 1) - doAssert 0 == usrsctp_set_upcall(conn.sctpSocket, handleConnect, cast[pointer](conn)) - doAssert 0 == conn.sctpSocket.usrsctp_setsockopt(IPPROTO_SCTP, SCTP_NODELAY, - addr nodelay, sizeof(nodelay).SockLen) - doAssert 0 == conn.sctpSocket.usrsctp_setsockopt(IPPROTO_SCTP, SCTP_RECVRCVINFO, - addr recvinfo, sizeof(recvinfo).SockLen) + let conn = SctpConn.new(await self.dtls.connect(raddr)) + conn.state = SctpConnecting + conn.sctpSocket = usrsctp_socket(AF_CONN, posix.SOCK_STREAM, IPPROTO_SCTP, nil, nil, 0, nil) + + if not conn.socketSetup(handleConnect): + raise newException(WebRtcError, "SCTP - Socket setup failed while connecting") + var sconn: Sockaddr_conn sconn.sconn_family = AF_CONN sconn.sconn_port = htons(sctpPort) sconn.sconn_addr = cast[pointer](conn) usrsctp_register_address(cast[pointer](conn)) conn.readLoop = conn.readLoopProc() + let connErr = self.usrsctpAwait: conn.sctpSocket.usrsctp_connect(cast[ptr SockAddr](addr sconn), SockLen(sizeof(sconn))) - doAssert 0 == connErr or errno == posix.EINPROGRESS, ($errno) - conn.state = SctpConnecting + if connErr != 0 and errno != posix.EINPROGRESS: + raise newException(WebRtcError, "SCTP - Connection failed " & $(sctpStrerror(errno))) + conn.connectEvent.clear() await conn.connectEvent.wait() - # TODO: check connection state, if closed throw an exception + if conn.state == SctpClosed: + raise newException(WebRtcError, "SCTP - Connection failed") self.connections[raddr] = conn return conn From bd5841cdfd14fcef92856c912abfe592b4cb987e Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 26 Jul 2024 16:46:08 +0200 Subject: [PATCH 14/69] chore: update comment --- webrtc/sctp/sctp_transport.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 729ccc6..e2ae8f5 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -67,7 +67,7 @@ proc handleAccept(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = conn.acceptEvent.fire() proc handleConnect(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = - # Callback procedure called when connecting + # Callback procedure called during usrsctp_connect let conn = cast[SctpConn](data) events = usrsctp_get_events(sock) From b77410bf78e0c07391f32bd2df0417770af5122e Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Tue, 20 Aug 2024 11:25:46 +0200 Subject: [PATCH 15/69] fix: ci --- webrtc/sctp/sctp_connection.nim | 2 +- webrtc/sctp/sctp_transport.nim | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index fc75ed8..7b015f7 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -130,7 +130,7 @@ proc new*(T: typedesc[SctpConn], conn: DtlsConn): T = state: SctpConnecting, connectEvent: AsyncEvent(), acceptEvent: AsyncEvent(), - raddr: conn.raddr, + raddr: conn.remoteAddress(), dataRecv: newAsyncQueue[SctpMessage]() ) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index e2ae8f5..e0762c8 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -60,10 +60,10 @@ proc handleAccept(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = if sctpSocket.isNil(): warn "usrsctp_accept fails", error = sctpStrerror(errno) - conn.state = SctpClosed + conn.state = SctpState.SctpClosed else: conn.sctpSocket = sctpSocket - conn.state = SctpConnected + conn.state = SctpState.SctpConnected conn.acceptEvent.fire() proc handleConnect(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = @@ -73,12 +73,12 @@ proc handleConnect(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = events = usrsctp_get_events(sock) trace "Handle Connect", events, state = conn.state - if conn.state == SctpConnecting: + if conn.state == SctpState.SctpConnecting: if bitand(events, SCTP_EVENT_ERROR) != 0: warn "Cannot connect", raddr = conn.raddr - conn.state = SctpClosed + conn.state = SctpState.SctpClosed elif bitand(events, SCTP_EVENT_WRITE) != 0: - conn.state = SctpConnected + conn.state = SctpState.SctpConnected doAssert 0 == usrsctp_set_upcall(conn.sctpSocket, recvCallback, data) conn.connectEvent.fire() else: @@ -108,7 +108,7 @@ proc new*(T: type Sctp, dtls: Dtls): T = self.timersHandler = timersHandler() self.dtls = dtls - usrsctp_init_nothreads(dtls.laddr.port.uint16, sendCallback, printf) + usrsctp_init_nothreads(dtls.localAddress.port.uint16, sendCallback, printf) discard usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_NONE) discard usrsctp_sysctl_set_sctp_ecn_enable(1) usrsctp_register_address(cast[pointer](self)) @@ -180,7 +180,7 @@ proc accept*(self: Sctp): Future[SctpConn] {.async.} = conn.readLoop = conn.readLoopProc() conn.acceptEvent.clear() await conn.acceptEvent.wait() - if conn.state == SctpConnected or conn.socketSetup(recvCallback): + if conn.state == SctpState.SctpConnected or conn.socketSetup(recvCallback): break await conn.close() @@ -213,7 +213,7 @@ proc connect*(self: Sctp, sctpPort: uint16 = 5000): Future[SctpConn] {.async.} = trace "Create Connection", raddr let conn = SctpConn.new(await self.dtls.connect(raddr)) - conn.state = SctpConnecting + conn.state = SctpState.SctpConnecting conn.sctpSocket = usrsctp_socket(AF_CONN, posix.SOCK_STREAM, IPPROTO_SCTP, nil, nil, 0, nil) if not conn.socketSetup(handleConnect): @@ -233,7 +233,7 @@ proc connect*(self: Sctp, conn.connectEvent.clear() await conn.connectEvent.wait() - if conn.state == SctpClosed: + if conn.state == SctpState.SctpClosed: raise newException(WebRtcError, "SCTP - Connection failed") self.connections[raddr] = conn return conn From ac65c6f822c1cab67e388688e264ac753503f070 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Tue, 20 Aug 2024 12:11:25 +0200 Subject: [PATCH 16/69] chore: remove useless field --- webrtc/sctp/sctp_transport.nim | 1 - 1 file changed, 1 deletion(-) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index e0762c8..0de0842 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -40,7 +40,6 @@ type isServer: bool sockServer: ptr socket pendingConnections: seq[SctpConn] - pendingConnections2: Table[SockAddr, SctpConn] sentFuture: Future[void] const IPPROTO_SCTP = 132 From df5eddd012cee8c86987d2c8609cb2dca1aa12b6 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Thu, 22 Aug 2024 14:59:02 +0200 Subject: [PATCH 17/69] feat: add testsctp & make it work --- tests/testsctp.nim | 56 +++++++++++++++++++++++++++++++++ webrtc/sctp/sctp_connection.nim | 14 ++++----- webrtc/sctp/sctp_transport.nim | 18 +++++------ webrtc/sctp/sctp_utils.nim | 23 +++++++++++++- 4 files changed, 92 insertions(+), 19 deletions(-) create mode 100644 tests/testsctp.nim diff --git a/tests/testsctp.nim b/tests/testsctp.nim new file mode 100644 index 0000000..b306d8d --- /dev/null +++ b/tests/testsctp.nim @@ -0,0 +1,56 @@ +# 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 ../webrtc/udp_transport +import ../webrtc/stun/stun_transport +import ../webrtc/dtls/dtls_transport +import ../webrtc/sctp/sctp_transport +import ../webrtc/sctp/sctp_connection +import ./asyncunit + +suite "SCTP": + teardown: + checkLeaks() + + asyncTest "Two SCTP nodes connecting to each other, then sending/receiving data": + let + 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) + sctp1 = Sctp.new(dtls1) + sctp2 = Sctp.new(dtls2) + sctp1.listen() + let conn1Fut = sctp1.accept() + let conn2 = await sctp2.connect(localAddr1) + let conn1 = await conn1Fut + + await conn1.write(@[1'u8, 2, 3, 4]) + check (await conn2.read()).data == @[1'u8, 2, 3, 4] + + await conn2.write(@[5'u8, 6, 7, 8]) + check (await conn1.read()).data == @[5'u8, 6, 7, 8] + + await conn1.write(@[10'u8, 11, 12, 13]) + await conn2.write(@[14'u8, 15, 16, 17]) + check (await conn1.read()).data == @[14'u8, 15, 16, 17] + check (await conn2.read()).data == @[10'u8, 11, 12, 13] + + await allFutures(conn1.close(), conn2.close()) + await allFutures(sctp1.close(), sctp2.close()) + await allFutures(dtls1.stop(), dtls2.stop()) + await allFutures(stun1.stop(), stun2.stop()) + await allFutures(udp1.close(), udp2.close()) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 7b015f7..29f0066 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -55,7 +55,7 @@ proc recvCallback*(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = conn = cast[SctpConn](data) events = usrsctp_get_events(sock) - trace "Handle Upcall", events + trace "Receive callback", events if bitand(events, SCTP_EVENT_READ) != 0: var message = SctpMessage( @@ -105,15 +105,13 @@ proc sendCallback*(ctx: pointer, tos: uint8, set_df: uint8): cint {.cdecl.} = # This proc is called by usrsctp everytime usrsctp tries to send data. - let data = usrsctp_dumppacket(buffer, length, SCTP_DUMP_OUTBOUND) - if data != nil: - trace "sendCallback", sctpPacket = data.getSctpPacket(), length - usrsctp_freedumpbuffer(data) - let sctpConn = cast[SctpConn](ctx) - let buf = @(buffer.makeOpenArray(byte, int(length))) + let + sctpConn = cast[SctpConn](ctx) + buf = @(buffer.makeOpenArray(byte, int(length))) + trace "sendCallback", sctpPacket = $(buf.getSctpPacket()) proc testSend() {.async.} = try: - trace "Send To", address = sctpConn.address + trace "Send To", address = sctpConn.raddr await sctpConn.conn.write(buf) except CatchableError as exc: trace "Send Failed", message = exc.msg diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 0de0842..8cddf34 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -110,22 +110,19 @@ proc new*(T: type Sctp, dtls: Dtls): T = usrsctp_init_nothreads(dtls.localAddress.port.uint16, sendCallback, printf) discard usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_NONE) discard usrsctp_sysctl_set_sctp_ecn_enable(1) - usrsctp_register_address(cast[pointer](self)) return self -proc stop*(self: Sctp) {.async.} = +proc close*(self: Sctp) {.async.} = # TODO: close every connections discard self.usrsctpAwait usrsctp_finish() proc readLoopProc(res: SctpConn) {.async.} = while true: - let - msg = await res.conn.read() - data = usrsctp_dumppacket(unsafeAddr msg[0], uint(msg.len), SCTP_DUMP_INBOUND) - if not data.isNil(): - trace "Receive data", remoteAddress = res.conn.raddr, - sctpPacket = data.getSctpPacket() - usrsctp_freedumpbuffer(data) + let msg = await res.conn.read() + if msg == @[]: + trace "Sctp read loop stopped, DTLS connection closed" + return + trace "Receive data", remoteAddress = res.conn.remoteAddress(), sctPacket = $(msg.getSctpPacket()) usrsctp_conninput(cast[pointer](res), unsafeAddr msg[0], uint(msg.len), 0) proc socketSetup( @@ -172,6 +169,7 @@ proc accept*(self: Sctp): Future[SctpConn] {.async.} = ## if not self.isServer: raise newException(WebRtcError, "SCTP - Not a server") + trace "Accept connection" var conn: SctpConn while true: conn = SctpConn.new(await self.dtls.accept()) @@ -179,7 +177,7 @@ proc accept*(self: Sctp): Future[SctpConn] {.async.} = conn.readLoop = conn.readLoopProc() conn.acceptEvent.clear() await conn.acceptEvent.wait() - if conn.state == SctpState.SctpConnected or conn.socketSetup(recvCallback): + if conn.state == SctpState.SctpConnected and conn.socketSetup(recvCallback): break await conn.close() diff --git a/webrtc/sctp/sctp_utils.nim b/webrtc/sctp/sctp_utils.nim index 57c31ed..64cbc26 100644 --- a/webrtc/sctp/sctp_utils.nim +++ b/webrtc/sctp/sctp_utils.nim @@ -27,10 +27,31 @@ type header*: SctpPacketHeader chunks*: seq[SctpChunk] +proc dataToString(data: seq[byte]): string = + if data.len() < 8: + return $data + result = "@[" + result &= $data[0] & ", " & $data[1] & ", " & $data[2] & ", " & $data[3] & " ... " + result &= $data[^4] & ", " & $data[^3] & ", " & $data[^2] & ", " & $data[^1] & "]" + +proc `$`*(packet: SctpPacketStructure): string = + result = "{header: {srcPort: " + result &= $(packet.header.srcPort) & ", dstPort: " + result &= $(packet.header.dstPort) & "}, chunks: @[" + var counter = 0 + for chunk in packet.chunks: + result &= "{type: " & $(chunk.chunkType) & ", len: " + result &= $(chunk.length) & ", data: " + result &= chunk.data.dataToString() + counter += 1 + if counter < packet.chunks.len(): + result &= ", " + result &= "]}" + proc getSctpPacket*(buffer: seq[byte]): SctpPacketStructure = # Only used for debugging/trace result.header = Binary.decode(buffer, SctpPacketHeader) - var size = sizeof(SctpPacketStructure) + var size = sizeof(SctpPacketHeader) while size < buffer.len: let chunk = Binary.decode(buffer[size..^1], SctpChunk) result.chunks.add(chunk) From 62cc2c7b1e05eb2cd5782c458261ab8cc05f7ed2 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 23 Aug 2024 11:39:48 +0200 Subject: [PATCH 18/69] chore: remove timer handler --- webrtc/sctp/sctp_transport.nim | 7 ------- webrtc/sctp/sctp_utils.nim | 2 ++ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 8cddf34..141972d 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -36,7 +36,6 @@ type laddr*: TransportAddress connections: Table[TransportAddress, SctpConn] gotConnection: AsyncEvent - timersHandler: Future[void] isServer: bool sockServer: ptr socket pendingConnections: seq[SctpConn] @@ -85,11 +84,6 @@ proc handleConnect(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = # -- Sctp -- -proc timersHandler() {.async.} = - while true: - await sleepAsync(500.milliseconds) - usrsctp_handle_timers(500) - proc stopServer*(self: Sctp) = if not self.isServer: trace "Try to close a client" @@ -104,7 +98,6 @@ proc stopServer*(self: Sctp) = proc new*(T: type Sctp, dtls: Dtls): T = var self = T() self.gotConnection = newAsyncEvent() - self.timersHandler = timersHandler() self.dtls = dtls usrsctp_init_nothreads(dtls.localAddress.port.uint16, sendCallback, printf) diff --git a/webrtc/sctp/sctp_utils.nim b/webrtc/sctp/sctp_utils.nim index 64cbc26..91c3b6f 100644 --- a/webrtc/sctp/sctp_utils.nim +++ b/webrtc/sctp/sctp_utils.nim @@ -28,6 +28,7 @@ type chunks*: seq[SctpChunk] proc dataToString(data: seq[byte]): string = + # Only used for debugging/trace if data.len() < 8: return $data result = "@[" @@ -35,6 +36,7 @@ proc dataToString(data: seq[byte]): string = result &= $data[^4] & ", " & $data[^3] & ", " & $data[^2] & ", " & $data[^1] & "]" proc `$`*(packet: SctpPacketStructure): string = + # Only used for debugging/trace result = "{header: {srcPort: " result &= $(packet.header.srcPort) & ", dstPort: " result &= $(packet.header.dstPort) & "}, chunks: @[" From 201a163d7da3a315b1d14fc8d7a4bd83a0003c02 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 23 Aug 2024 12:51:20 +0200 Subject: [PATCH 19/69] feat: add async exception tracking --- webrtc/sctp/sctp_connection.nim | 12 ++++++------ webrtc/sctp/sctp_transport.nim | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 29f0066..6d25b96 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -44,7 +44,7 @@ type raddr*: TransportAddress sctpSocket*: ptr socket dataRecv*: AsyncQueue[SctpMessage] - sentFuture*: Future[void] + sentFuture*: Future[void].Raising([CancelledError]) # -- usrsctp send and receive callback -- @@ -109,7 +109,7 @@ proc sendCallback*(ctx: pointer, sctpConn = cast[SctpConn](ctx) buf = @(buffer.makeOpenArray(byte, int(length))) trace "sendCallback", sctpPacket = $(buf.getSctpPacket()) - proc testSend() {.async.} = + proc testSend() {.async: (raises: [CancelledError]).} = try: trace "Send To", address = sctpConn.raddr await sctpConn.conn.write(buf) @@ -132,13 +132,13 @@ proc new*(T: typedesc[SctpConn], conn: DtlsConn): T = dataRecv: newAsyncQueue[SctpMessage]() ) -proc read*(self: SctpConn): Future[SctpMessage] {.async.} = +proc read*(self: SctpConn): Future[SctpMessage] {.async: (raises: [CancelledError]).} = # Used by DataChannel, returns SctpMessage in order to get the stream # and protocol ids return await self.dataRecv.popFirst() proc write*(self: SctpConn, buf: seq[byte], - sendParams = default(SctpMessageParameters)) {.async.} = + sendParams = default(SctpMessageParameters)) {.async: (raises: [CancelledError, WebRtcError]).} = # Used by DataChannel, writes buf on the Dtls connection. trace "Write", buf @@ -162,10 +162,10 @@ proc write*(self: SctpConn, buf: seq[byte], if sendvErr < 0: raise newException(WebRtcError, $(sctpStrerror(sendvErr))) -proc write*(self: SctpConn, s: string) {.async.} = +proc write*(self: SctpConn, s: string) {.async: (raises: [CancelledError, WebRtcError]).} = await self.write(s.toBytes()) -proc close*(self: SctpConn) {.async.} = +proc close*(self: SctpConn) {.async: (raises: [CancelledError]).} = self.usrsctpAwait: self.sctpSocket.usrsctp_close() usrsctp_deregister_address(cast[pointer](self)) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 141972d..538b81b 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -39,7 +39,7 @@ type isServer: bool sockServer: ptr socket pendingConnections: seq[SctpConn] - sentFuture: Future[void] + sentFuture: Future[void].Raising([CancelledError]) const IPPROTO_SCTP = 132 @@ -105,11 +105,11 @@ proc new*(T: type Sctp, dtls: Dtls): T = discard usrsctp_sysctl_set_sctp_ecn_enable(1) return self -proc close*(self: Sctp) {.async.} = +proc close*(self: Sctp) {.async: (raises: [CancelledError]).} = # TODO: close every connections discard self.usrsctpAwait usrsctp_finish() -proc readLoopProc(res: SctpConn) {.async.} = +proc readLoopProc(res: SctpConn) {.async: (raises: [CancelledError, WebRtcError]).} = while true: let msg = await res.conn.read() if msg == @[]: @@ -157,7 +157,7 @@ proc socketSetup( return false return true -proc accept*(self: Sctp): Future[SctpConn] {.async.} = +proc accept*(self: Sctp): Future[SctpConn] {.async: (raises: [CancelledError, WebRtcError]).} = ## Accept an Sctp Connection ## if not self.isServer: @@ -200,7 +200,7 @@ proc listen*(self: Sctp, sctpPort: uint16 = 5000) = proc connect*(self: Sctp, raddr: TransportAddress, - sctpPort: uint16 = 5000): Future[SctpConn] {.async.} = + sctpPort: uint16 = 5000): Future[SctpConn] {.async: (raises: [CancelledError, WebRtcError]).} = trace "Create Connection", raddr let conn = SctpConn.new(await self.dtls.connect(raddr)) conn.state = SctpState.SctpConnecting From 4a59265071d8c69cf73d702c30ed7a59328a9598 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 23 Aug 2024 12:58:20 +0200 Subject: [PATCH 20/69] style: pass nph on sctp files --- webrtc/sctp/sctp_connection.nim | 90 +++++++++++++++++++-------------- webrtc/sctp/sctp_transport.nim | 73 +++++++++++++------------- webrtc/sctp/sctp_utils.nim | 14 +++-- 3 files changed, 100 insertions(+), 77 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 6d25b96..8134b2c 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -8,11 +8,8 @@ # those terms. import posix, bitops, sequtils -import chronos, chronicles, stew/[ptrops, endians2, byteutils] -import usrsctp -import ./sctp_utils -import ../errors -import ../dtls/dtls_connection +import usrsctp, chronos, chronicles, stew/[ptrops, endians2, byteutils] +import ./sctp_utils, ../errors, ../dtls/dtls_connection logScope: topics = "webrtc sctp_connection" @@ -58,35 +55,35 @@ proc recvCallback*(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = trace "Receive callback", events if bitand(events, SCTP_EVENT_READ) != 0: var - message = SctpMessage( - data: newSeq[byte](4096) - ) + message = SctpMessage(data: newSeq[byte](4096)) address: Sockaddr_storage rn: sctp_recvv_rn addressLen = sizeof(Sockaddr_storage).SockLen rnLen = sizeof(sctp_recvv_rn).SockLen infotype: uint flags: int - let n = sock.usrsctp_recvv(cast[pointer](addr message.data[0]), - message.data.len.uint, - cast[ptr SockAddr](addr address), - cast[ptr SockLen](addr addressLen), - cast[pointer](addr message.info), - cast[ptr SockLen](addr rnLen), - cast[ptr cuint](addr infotype), - cast[ptr cint](addr flags)) + let n = sock.usrsctp_recvv( + cast[pointer](addr message.data[0]), + message.data.len.uint, + cast[ptr SockAddr](addr address), + cast[ptr SockLen](addr addressLen), + cast[pointer](addr message.info), + cast[ptr SockLen](addr rnLen), + cast[ptr cuint](addr infotype), + cast[ptr cint](addr flags), + ) if n < 0: trace "usrsctp_recvv", error = sctpStrerror(n) # TODO: should close return elif n > 0: # It might be necessary to check if infotype == SCTP_RECVV_RCVINFO - message.data.delete(n..", gcsafe.} +proc printf( + format: cstring +) {.cdecl, importc: "printf", varargs, header: "", gcsafe.} -type - Sctp* = ref object - dtls: Dtls - laddr*: TransportAddress - connections: Table[TransportAddress, SctpConn] - gotConnection: AsyncEvent - isServer: bool - sockServer: ptr socket - pendingConnections: seq[SctpConn] - sentFuture: Future[void].Raising([CancelledError]) +type Sctp* = ref object + dtls: Dtls + laddr*: TransportAddress + connections: Table[TransportAddress, SctpConn] + gotConnection: AsyncEvent + isServer: bool + sockServer: ptr socket + pendingConnections: seq[SctpConn] + sentFuture: Future[void].Raising([CancelledError]) const IPPROTO_SCTP = 132 @@ -53,7 +52,8 @@ proc handleAccept(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = slen: Socklen = sizeof(Sockaddr_conn).uint32 let sctp = cast[Sctp](data) - sctpSocket = usrsctp_accept(sctp.sockServer, cast[ptr SockAddr](addr sconn), addr slen) + sctpSocket = + usrsctp_accept(sctp.sockServer, cast[ptr SockAddr](addr sconn), addr slen) conn = cast[SctpConn](sconn.sconn_addr) if sctpSocket.isNil(): @@ -115,12 +115,12 @@ proc readLoopProc(res: SctpConn) {.async: (raises: [CancelledError, WebRtcError] if msg == @[]: trace "Sctp read loop stopped, DTLS connection closed" return - trace "Receive data", remoteAddress = res.conn.remoteAddress(), sctPacket = $(msg.getSctpPacket()) + trace "Receive data", + remoteAddress = res.conn.remoteAddress(), sctPacket = $(msg.getSctpPacket()) usrsctp_conninput(cast[pointer](res), unsafeAddr msg[0], uint(msg.len), 0) proc socketSetup( - conn: SctpConn, - callback: proc (a1: ptr socket, a2: pointer, a3: cint) {.cdecl.} + conn: SctpConn, callback: proc(a1: ptr socket, a2: pointer, a3: cint) {.cdecl.} ): bool = var errorCode = conn.sctpSocket.usrsctp_set_non_blocking(1) @@ -137,27 +137,23 @@ proc socketSetup( return false errorCode = conn.sctpSocket.usrsctp_setsockopt( - IPPROTO_SCTP, - SCTP_NODELAY, - addr nodelay, - sizeof(nodelay).SockLen, + IPPROTO_SCTP, SCTP_NODELAY, addr nodelay, sizeof(nodelay).SockLen ) if errorCode != 0: warn "usrsctp_setsockopt nodelay fails", error = sctpStrerror(errorCode) return false errorCode = conn.sctpSocket.usrsctp_setsockopt( - IPPROTO_SCTP, - SCTP_RECVRCVINFO, - addr recvinfo, - sizeof(recvinfo).SockLen, + IPPROTO_SCTP, SCTP_RECVRCVINFO, addr recvinfo, sizeof(recvinfo).SockLen ) if errorCode != 0: warn "usrsctp_setsockopt recvinfo fails", error = sctpStrerror(errorCode) return false return true -proc accept*(self: Sctp): Future[SctpConn] {.async: (raises: [CancelledError, WebRtcError]).} = +proc accept*( + self: Sctp +): Future[SctpConn] {.async: (raises: [CancelledError, WebRtcError]).} = ## Accept an Sctp Connection ## if not self.isServer: @@ -193,18 +189,20 @@ proc listen*(self: Sctp, sctpPort: uint16 = 5000) = sin.sin_family = posix.AF_INET.uint16 sin.sin_port = htons(sctpPort) sin.sin_addr.s_addr = htonl(INADDR_ANY) - doAssert 0 == usrsctp_bind(sock, cast[ptr SockAddr](addr sin), SockLen(sizeof(Sockaddr_in))) + doAssert 0 == + usrsctp_bind(sock, cast[ptr SockAddr](addr sin), SockLen(sizeof(Sockaddr_in))) doAssert 0 >= usrsctp_listen(sock, 1) doAssert 0 == sock.usrsctp_set_upcall(handleAccept, cast[pointer](self)) self.sockServer = sock -proc connect*(self: Sctp, - raddr: TransportAddress, - sctpPort: uint16 = 5000): Future[SctpConn] {.async: (raises: [CancelledError, WebRtcError]).} = +proc connect*( + self: Sctp, raddr: TransportAddress, sctpPort: uint16 = 5000 +): Future[SctpConn] {.async: (raises: [CancelledError, WebRtcError]).} = trace "Create Connection", raddr let conn = SctpConn.new(await self.dtls.connect(raddr)) conn.state = SctpState.SctpConnecting - conn.sctpSocket = usrsctp_socket(AF_CONN, posix.SOCK_STREAM, IPPROTO_SCTP, nil, nil, 0, nil) + conn.sctpSocket = + usrsctp_socket(AF_CONN, posix.SOCK_STREAM, IPPROTO_SCTP, nil, nil, 0, nil) if not conn.socketSetup(handleConnect): raise newException(WebRtcError, "SCTP - Socket setup failed while connecting") @@ -217,9 +215,12 @@ proc connect*(self: Sctp, conn.readLoop = conn.readLoopProc() let connErr = self.usrsctpAwait: - conn.sctpSocket.usrsctp_connect(cast[ptr SockAddr](addr sconn), SockLen(sizeof(sconn))) + conn.sctpSocket.usrsctp_connect( + cast[ptr SockAddr](addr sconn), SockLen(sizeof(sconn)) + ) if connErr != 0 and errno != posix.EINPROGRESS: - raise newException(WebRtcError, "SCTP - Connection failed " & $(sctpStrerror(errno))) + raise + newException(WebRtcError, "SCTP - Connection failed " & $(sctpStrerror(errno))) conn.connectEvent.clear() await conn.connectEvent.wait() diff --git a/webrtc/sctp/sctp_utils.nim b/webrtc/sctp/sctp_utils.nim index 91c3b6f..1d92fee 100644 --- a/webrtc/sctp/sctp_utils.nim +++ b/webrtc/sctp/sctp_utils.nim @@ -9,7 +9,9 @@ import binary_serialization -proc sctpStrerror*(error: int): cstring {.importc: "strerror", cdecl, header: "".} +proc sctpStrerror*( + error: int +): cstring {.importc: "strerror", cdecl, header: "".} type # These three objects are used for debugging/trace only @@ -18,11 +20,13 @@ type flag*: uint8 length* {.bin_value: it.data.len() + 4.}: uint16 data* {.bin_len: it.length - 4.}: seq[byte] + SctpPacketHeader* = object srcPort*: uint16 dstPort*: uint16 verifTag*: uint32 checksum*: uint32 + SctpPacketStructure* = object header*: SctpPacketHeader chunks*: seq[SctpChunk] @@ -55,7 +59,7 @@ proc getSctpPacket*(buffer: seq[byte]): SctpPacketStructure = result.header = Binary.decode(buffer, SctpPacketHeader) var size = sizeof(SctpPacketHeader) while size < buffer.len: - let chunk = Binary.decode(buffer[size..^1], SctpChunk) + let chunk = Binary.decode(buffer[size ..^ 1], SctpChunk) result.chunks.add(chunk) size.inc(chunk.length.int) while size mod 4 != 0: @@ -70,8 +74,10 @@ template usrsctpAwait*(self: untyped, body: untyped): untyped = self.sentFuture = nil when type(body) is void: (body) - if self.sentFuture != nil: await self.sentFuture + if self.sentFuture != nil: + await self.sentFuture else: let res = (body) - if self.sentFuture != nil: await self.sentFuture + if self.sentFuture != nil: + await self.sentFuture res From 96a363e5063346cc8362e053c8e4563060bc8299 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 23 Aug 2024 14:36:23 +0200 Subject: [PATCH 21/69] feat: add sctp tracker counter --- webrtc/sctp/sctp_connection.nim | 4 +++- webrtc/sctp/sctp_transport.nim | 11 ++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 8134b2c..5fa4a13 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -14,6 +14,8 @@ import ./sctp_utils, ../errors, ../dtls/dtls_connection logScope: topics = "webrtc sctp_connection" +const SctpConnTracker* = "webrtc.sctp.conn" + # TODO: closing connection if usrsctp_recv / usrsctp_read fails type SctpState* = enum @@ -37,7 +39,6 @@ type state*: SctpState connectEvent*: AsyncEvent acceptEvent*: AsyncEvent - readLoop*: Future[void] raddr*: TransportAddress sctpSocket*: ptr socket dataRecv*: AsyncQueue[SctpMessage] @@ -185,3 +186,4 @@ proc close*(self: SctpConn) {.async: (raises: [CancelledError]).} = self.usrsctpAwait: self.sctpSocket.usrsctp_close() usrsctp_deregister_address(cast[pointer](self)) + untrackCounter(SctpConnTracker) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 01dc2f7..d22a362 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -14,6 +14,10 @@ import export chronicles +const + SctpTransportTracker* = "webrtc.sctp.transport" + IPPROTO_SCTP = 132 + logScope: topics = "webrtc sctp" @@ -32,7 +36,6 @@ proc printf( type Sctp* = ref object dtls: Dtls - laddr*: TransportAddress connections: Table[TransportAddress, SctpConn] gotConnection: AsyncEvent isServer: bool @@ -40,8 +43,6 @@ type Sctp* = ref object pendingConnections: seq[SctpConn] sentFuture: Future[void].Raising([CancelledError]) -const IPPROTO_SCTP = 132 - # -- usrsctp accept and connect callbacks -- proc handleAccept(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = @@ -103,10 +104,12 @@ proc new*(T: type Sctp, dtls: Dtls): T = usrsctp_init_nothreads(dtls.localAddress.port.uint16, sendCallback, printf) discard usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_NONE) discard usrsctp_sysctl_set_sctp_ecn_enable(1) + trackCounter(SctpTransportTracker) return self proc close*(self: Sctp) {.async: (raises: [CancelledError]).} = # TODO: close every connections + untrackCounter(SctpTransportTracker) discard self.usrsctpAwait usrsctp_finish() proc readLoopProc(res: SctpConn) {.async: (raises: [CancelledError, WebRtcError]).} = @@ -171,6 +174,7 @@ proc accept*( await conn.close() self.connections[conn.raddr] = conn + trackCounter(SctpConnTracker) return conn proc listen*(self: Sctp, sctpPort: uint16 = 5000) = @@ -227,4 +231,5 @@ proc connect*( if conn.state == SctpState.SctpClosed: raise newException(WebRtcError, "SCTP - Connection failed") self.connections[raddr] = conn + trackCounter(SctpConnTracker) return conn From 197c0c3a57a9f5cca18e0920f1a7485ad3926b4a Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 23 Aug 2024 15:13:51 +0200 Subject: [PATCH 22/69] fix: readloop is back --- webrtc/sctp/sctp_connection.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 5fa4a13..d951ace 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -39,6 +39,7 @@ type state*: SctpState connectEvent*: AsyncEvent acceptEvent*: AsyncEvent + readLoop*: Future[void] raddr*: TransportAddress sctpSocket*: ptr socket dataRecv*: AsyncQueue[SctpMessage] From 722db0f2ffdcf037547414b677b6d3589af1e830 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Mon, 26 Aug 2024 12:44:27 +0200 Subject: [PATCH 23/69] fix: windows ci --- webrtc/sctp/sctp_connection.nim | 2 +- webrtc/sctp/sctp_transport.nim | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index d951ace..392e918 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -7,7 +7,7 @@ # This file may not be copied, modified, or distributed except according to # those terms. -import posix, bitops, sequtils +import nativesockets, bitops, sequtils import usrsctp, chronos, chronicles, stew/[ptrops, endians2, byteutils] import ./sctp_utils, ../errors, ../dtls/dtls_connection diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index d22a362..8784240 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -7,13 +7,14 @@ # This file may not be copied, modified, or distributed except according to # those terms. -import tables, bitops, posix, strutils +import tables, bitops, nativesockets, strutils import usrsctp, chronos, chronicles import ./[sctp_connection, sctp_utils], ../errors, ../dtls/[dtls_transport, dtls_connection] export chronicles +var errno {.importc, header: "".}: cint ## error variable const SctpTransportTracker* = "webrtc.sctp.transport" IPPROTO_SCTP = 132 @@ -186,11 +187,11 @@ proc listen*(self: Sctp, sctpPort: uint16 = 5000) = doAssert 0 == usrsctp_sysctl_set_sctp_blackhole(2) doAssert 0 == usrsctp_sysctl_set_sctp_no_csum_on_loopback(0) doAssert 0 == usrsctp_sysctl_set_sctp_delayed_sack_time_default(0) - let sock = usrsctp_socket(AF_CONN, posix.SOCK_STREAM, IPPROTO_SCTP, nil, nil, 0, nil) + let sock = usrsctp_socket(AF_CONN, SOCK_STREAM.toInt(), IPPROTO_SCTP, nil, nil, 0, nil) var on: int = 1 doAssert 0 == usrsctp_set_non_blocking(sock, 1) var sin: Sockaddr_in - sin.sin_family = posix.AF_INET.uint16 + sin.sin_family = AF_INET.uint16 sin.sin_port = htons(sctpPort) sin.sin_addr.s_addr = htonl(INADDR_ANY) doAssert 0 == @@ -206,7 +207,7 @@ proc connect*( let conn = SctpConn.new(await self.dtls.connect(raddr)) conn.state = SctpState.SctpConnecting conn.sctpSocket = - usrsctp_socket(AF_CONN, posix.SOCK_STREAM, IPPROTO_SCTP, nil, nil, 0, nil) + usrsctp_socket(AF_CONN, SOCK_STREAM.toInt(), IPPROTO_SCTP, nil, nil, 0, nil) if not conn.socketSetup(handleConnect): raise newException(WebRtcError, "SCTP - Socket setup failed while connecting") @@ -222,7 +223,7 @@ proc connect*( conn.sctpSocket.usrsctp_connect( cast[ptr SockAddr](addr sconn), SockLen(sizeof(sconn)) ) - if connErr != 0 and errno != posix.EINPROGRESS: + if connErr != 0 and errno != nativesockets.EINPROGRESS: raise newException(WebRtcError, "SCTP - Connection failed " & $(sctpStrerror(errno))) From 54612e9a0b05079fbb07958bb4f537df97f159a9 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Mon, 26 Aug 2024 13:11:35 +0200 Subject: [PATCH 24/69] chore: remove raddr from SctpConn --- webrtc/sctp/sctp_connection.nim | 17 ++++++++++------- webrtc/sctp/sctp_transport.nim | 4 ++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 392e918..51d18ad 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -40,11 +40,15 @@ type connectEvent*: AsyncEvent acceptEvent*: AsyncEvent readLoop*: Future[void] - raddr*: TransportAddress sctpSocket*: ptr socket dataRecv*: AsyncQueue[SctpMessage] sentFuture*: Future[void].Raising([CancelledError]) +proc remoteAddress*(self: SctpConn): TransportAddress = + if self.conn.isNil(): + raise newException(WebRtcError, "SCTP - Connection not set") + return self.conn.remoteAddress() + # -- usrsctp send and receive callback -- proc recvCallback*(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = @@ -103,17 +107,17 @@ proc sendCallback*( ): cint {.cdecl.} = # This proc is called by usrsctp everytime usrsctp tries to send data. let - sctpConn = cast[SctpConn](ctx) + conn = cast[SctpConn](ctx) buf = @(buffer.makeOpenArray(byte, int(length))) trace "sendCallback", sctpPacket = $(buf.getSctpPacket()) proc testSend() {.async: (raises: [CancelledError]).} = try: - trace "Send To", address = sctpConn.raddr - await sctpConn.conn.write(buf) + trace "Send To", address = conn.remoteAddress() + await conn.conn.write(buf) except CatchableError as exc: trace "Send Failed", message = exc.msg - sctpConn.sentFuture = testSend() + conn.sentFuture = testSend() proc toFlags(params: SctpMessageParameters): uint16 = if params.endOfRecord: @@ -127,7 +131,6 @@ proc new*(T: typedesc[SctpConn], conn: DtlsConn): T = state: SctpConnecting, connectEvent: AsyncEvent(), acceptEvent: AsyncEvent(), - raddr: conn.remoteAddress(), dataRecv: newAsyncQueue[SctpMessage](), ) @@ -176,7 +179,7 @@ proc write*( 0, ) if sendvErr < 0: - raise newException(WebRtcError, $(sctpStrerror(sendvErr))) + raise newException(WebRtcError, "SCTP - " & $(sctpStrerror(sendvErr))) proc write*( self: SctpConn, s: string diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 8784240..606b877 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -75,7 +75,7 @@ proc handleConnect(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = trace "Handle Connect", events, state = conn.state if conn.state == SctpState.SctpConnecting: if bitand(events, SCTP_EVENT_ERROR) != 0: - warn "Cannot connect", raddr = conn.raddr + warn "Cannot connect", raddr = conn.remoteAddress() conn.state = SctpState.SctpClosed elif bitand(events, SCTP_EVENT_WRITE) != 0: conn.state = SctpState.SctpConnected @@ -174,7 +174,7 @@ proc accept*( break await conn.close() - self.connections[conn.raddr] = conn + self.connections[conn.remoteAddress()] = conn trackCounter(SctpConnTracker) return conn From ecc4fbb4d0f17be5fd4e445fc1cb8a1de5efb6a1 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Mon, 26 Aug 2024 13:19:40 +0200 Subject: [PATCH 25/69] fix: defined EINPROGRESS when using windows --- webrtc/sctp/sctp_transport.nim | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 606b877..ef53c88 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -14,7 +14,9 @@ import export chronicles -var errno {.importc, header: "".}: cint ## error variable +when defined(windows): + const EINPROGRESS = WASEINPROGRESS + const SctpTransportTracker* = "webrtc.sctp.transport" IPPROTO_SCTP = 132 @@ -31,6 +33,7 @@ logScope: # - Replace doAssert by a proper exception management # - Find a clean way to manage SCTP ports +var errno {.importc, header: "".}: cint ## error variable proc printf( format: cstring ) {.cdecl, importc: "printf", varargs, header: "", gcsafe.} From d6b00e5dd6f4960fe53083442d6a3d1917de98e4 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Mon, 26 Aug 2024 13:30:21 +0200 Subject: [PATCH 26/69] fix: windows & macos ci --- webrtc/sctp/sctp_transport.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index ef53c88..51047ce 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -15,7 +15,7 @@ import export chronicles when defined(windows): - const EINPROGRESS = WASEINPROGRESS + const EINPROGRESS = WSAEINPROGRESS const SctpTransportTracker* = "webrtc.sctp.transport" @@ -194,7 +194,7 @@ proc listen*(self: Sctp, sctpPort: uint16 = 5000) = var on: int = 1 doAssert 0 == usrsctp_set_non_blocking(sock, 1) var sin: Sockaddr_in - sin.sin_family = AF_INET.uint16 + sin.sin_family = type(sin.sin_family)(AF_INET) sin.sin_port = htons(sctpPort) sin.sin_addr.s_addr = htonl(INADDR_ANY) doAssert 0 == From 6e96abe2158ec298bf3d4efdbfd1010a1c818c8d Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Mon, 26 Aug 2024 15:10:44 +0200 Subject: [PATCH 27/69] feat: add testsctp.nim to runalltest.nim --- tests/runalltests.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/runalltests.nim b/tests/runalltests.nim index 3a49806..9334531 100644 --- a/tests/runalltests.nim +++ b/tests/runalltests.nim @@ -11,3 +11,4 @@ import teststun import testdtls +import testsctp From 785d2bbd7a22e51b2816f863a2d27aef68cb6ca0 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Mon, 26 Aug 2024 17:09:00 +0200 Subject: [PATCH 28/69] chore: turn on sctp debug --- tests/runalltests.nim | 2 ++ webrtc/sctp/sctp_transport.nim | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/runalltests.nim b/tests/runalltests.nim index 9334531..9862719 100644 --- a/tests/runalltests.nim +++ b/tests/runalltests.nim @@ -9,6 +9,8 @@ {.used.} +{.passc: "-DSCTP_DEBUG".} + import teststun import testdtls import testsctp diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 51047ce..aac6f98 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -106,7 +106,7 @@ proc new*(T: type Sctp, dtls: Dtls): T = self.dtls = dtls usrsctp_init_nothreads(dtls.localAddress.port.uint16, sendCallback, printf) - discard usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_NONE) + discard usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_ALL) discard usrsctp_sysctl_set_sctp_ecn_enable(1) trackCounter(SctpTransportTracker) return self @@ -228,7 +228,7 @@ proc connect*( ) if connErr != 0 and errno != nativesockets.EINPROGRESS: raise - newException(WebRtcError, "SCTP - Connection failed " & $(sctpStrerror(errno))) + newException(WebRtcError, "SCTP - Connection failed: " & $(sctpStrerror(errno))) conn.connectEvent.clear() await conn.connectEvent.wait() From c8ebe7e0073e4476331e9c65840e1c300df0a305 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Mon, 26 Aug 2024 17:13:39 +0200 Subject: [PATCH 29/69] fix: windows EINPROGRESS error --- webrtc/sctp/sctp_transport.nim | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index aac6f98..0d653c9 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -15,9 +15,15 @@ import export chronicles when defined(windows): - const EINPROGRESS = WSAEINPROGRESS + const = WSAEINPROGRESS +else const + SctpEINPROGRESS = + when defined(windows): + WSAEINPROGRESS + else: + nativesockets.EINPROGRESS SctpTransportTracker* = "webrtc.sctp.transport" IPPROTO_SCTP = 132 @@ -226,7 +232,7 @@ proc connect*( conn.sctpSocket.usrsctp_connect( cast[ptr SockAddr](addr sconn), SockLen(sizeof(sconn)) ) - if connErr != 0 and errno != nativesockets.EINPROGRESS: + if connErr != 0 and errno != SctpEINPROGRESS: raise newException(WebRtcError, "SCTP - Connection failed: " & $(sctpStrerror(errno))) From 2b1228f0c4ee8ce3e73d513e3c627e49fa3c1f73 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Mon, 26 Aug 2024 17:14:59 +0200 Subject: [PATCH 30/69] fix: debug all --- webrtc/sctp/sctp_transport.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 0d653c9..e22259b 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -112,7 +112,7 @@ proc new*(T: type Sctp, dtls: Dtls): T = self.dtls = dtls usrsctp_init_nothreads(dtls.localAddress.port.uint16, sendCallback, printf) - discard usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_ALL) + discard usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_ALL.uint32) discard usrsctp_sysctl_set_sctp_ecn_enable(1) trackCounter(SctpTransportTracker) return self From 108841d2b458a9de7e62ed2ae1634f52e08cd451 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Mon, 26 Aug 2024 17:21:49 +0200 Subject: [PATCH 31/69] fix: remove useless code --- webrtc/sctp/sctp_transport.nim | 4 ---- 1 file changed, 4 deletions(-) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index e22259b..50f1ee7 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -14,10 +14,6 @@ import export chronicles -when defined(windows): - const = WSAEINPROGRESS -else - const SctpEINPROGRESS = when defined(windows): From 5fcc26da4e9962d49489a98dc8e469eaf612825a Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Tue, 27 Aug 2024 10:48:09 +0200 Subject: [PATCH 32/69] fix: change EINPROGRESS again --- webrtc/sctp/sctp_transport.nim | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 50f1ee7..01c7b08 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -15,11 +15,6 @@ import export chronicles const - SctpEINPROGRESS = - when defined(windows): - WSAEINPROGRESS - else: - nativesockets.EINPROGRESS SctpTransportTracker* = "webrtc.sctp.transport" IPPROTO_SCTP = 132 @@ -228,7 +223,7 @@ proc connect*( conn.sctpSocket.usrsctp_connect( cast[ptr SockAddr](addr sconn), SockLen(sizeof(sconn)) ) - if connErr != 0 and errno != SctpEINPROGRESS: + if connErr != 0 and errno != chronos.EINPROGRESS.cint: raise newException(WebRtcError, "SCTP - Connection failed: " & $(sctpStrerror(errno))) From 57cd0f0ad9ab249d1f08d49a86656a66c119c134 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Tue, 27 Aug 2024 11:27:08 +0200 Subject: [PATCH 33/69] fix: change EINPROGRESS again --- webrtc/sctp/sctp_transport.nim | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 01c7b08..25d7e26 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -15,6 +15,11 @@ import export chronicles const + SctpEINPROGRESS = + when defined(windows): + chronos.WSAEINPROGRESS.cint + else: + chronos.EINPROGRESS.cint SctpTransportTracker* = "webrtc.sctp.transport" IPPROTO_SCTP = 132 @@ -223,7 +228,7 @@ proc connect*( conn.sctpSocket.usrsctp_connect( cast[ptr SockAddr](addr sconn), SockLen(sizeof(sconn)) ) - if connErr != 0 and errno != chronos.EINPROGRESS.cint: + if connErr != 0 and errno != SctpEINPROGRESS: raise newException(WebRtcError, "SCTP - Connection failed: " & $(sctpStrerror(errno))) From e914f69247fcbc3b706ab5f1afddbd80997440b0 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Tue, 27 Aug 2024 11:58:34 +0200 Subject: [PATCH 34/69] fix: change EINPROGRESS again --- webrtc/sctp/sctp_transport.nim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 25d7e26..967d9e0 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -17,7 +17,8 @@ export chronicles const SctpEINPROGRESS = when defined(windows): - chronos.WSAEINPROGRESS.cint + import winlean + winlean.WSAEINPROGRESS.cint else: chronos.EINPROGRESS.cint SctpTransportTracker* = "webrtc.sctp.transport" From 183d8d5a4a4d52b66d9f25b5befb6d3840816050 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Tue, 27 Aug 2024 15:15:45 +0200 Subject: [PATCH 35/69] feat: add a test --- tests/testsctp.nim | 108 ++++++++++++++++++++++++++++++++------------- 1 file changed, 77 insertions(+), 31 deletions(-) diff --git a/tests/testsctp.nim b/tests/testsctp.nim index b306d8d..e659021 100644 --- a/tests/testsctp.nim +++ b/tests/testsctp.nim @@ -21,36 +21,82 @@ suite "SCTP": teardown: checkLeaks() + type + SctpStackForTest = object + localAddress: TransportAddress + udp: UdpTransport + stun: Stun + dtls: Dtls + sctp: Sctp + + proc initSctpStack(localAddress: TransportAddress): SctpStackForTest = + result.localAddress = localAddress + result.udp = UdpTransport.new(result.localAddress) + result.stun = Stun.new(result.udp) + result.dtls = Dtls.new(result.stun) + result.sctp = Sctp.new(result.dtls) + result.sctp.listen() + + proc closeSctpStack(self: SctpStackForTest) {.async: (raises: [CancelledError]).} = + await self.sctp.close() + await self.dtls.stop() + await self.stun.stop() + await self.udp.close() + asyncTest "Two SCTP nodes connecting to each other, then sending/receiving data": + var + sctpServer = initSctpStack(initTAddress("127.0.0.1:4444")) + sctpClient = initSctpStack(initTAddress("127.0.0.1:5555")) + let + serverConnFut = sctpServer.sctp.accept() + clientConn = await sctpClient.sctp.connect(sctpServer.localAddress) + serverConn = await serverConnFut + + await clientConn.write(@[1'u8, 2, 3, 4]) + check (await serverConn.read()).data == @[1'u8, 2, 3, 4] + + await serverConn.write(@[5'u8, 6, 7, 8]) + check (await clientConn.read()).data == @[5'u8, 6, 7, 8] + + await clientConn.write(@[10'u8, 11, 12, 13]) + await serverConn.write(@[14'u8, 15, 16, 17]) + check (await clientConn.read()).data == @[14'u8, 15, 16, 17] + check (await serverConn.read()).data == @[10'u8, 11, 12, 13] + + await allFutures(clientConn.close(), serverConn.close()) + await allFutures(sctpClient.closeSctpStack(), sctpServer.closeSctpStack()) + + asyncTest "Two DTLS nodes connecting to the same DTLS server, sending/receiving data": + var + sctpServer = initSctpStack(initTAddress("127.0.0.1:4444")) + sctpClient1 = initSctpStack(initTAddress("127.0.0.1:5555")) + sctpClient2 = initSctpStack(initTAddress("127.0.0.1:6666")) let - 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) - sctp1 = Sctp.new(dtls1) - sctp2 = Sctp.new(dtls2) - sctp1.listen() - let conn1Fut = sctp1.accept() - let conn2 = await sctp2.connect(localAddr1) - let conn1 = await conn1Fut - - await conn1.write(@[1'u8, 2, 3, 4]) - check (await conn2.read()).data == @[1'u8, 2, 3, 4] - - await conn2.write(@[5'u8, 6, 7, 8]) - check (await conn1.read()).data == @[5'u8, 6, 7, 8] - - await conn1.write(@[10'u8, 11, 12, 13]) - await conn2.write(@[14'u8, 15, 16, 17]) - check (await conn1.read()).data == @[14'u8, 15, 16, 17] - check (await conn2.read()).data == @[10'u8, 11, 12, 13] - - await allFutures(conn1.close(), conn2.close()) - await allFutures(sctp1.close(), sctp2.close()) - await allFutures(dtls1.stop(), dtls2.stop()) - await allFutures(stun1.stop(), stun2.stop()) - await allFutures(udp1.close(), udp2.close()) + serverConn1Fut = sctpServer.sctp.accept() + serverConn2Fut = sctpServer.sctp.accept() + clientConn1 = await sctpClient1.sctp.connect(sctpServer.localAddress) + clientConn2 = await sctpClient2.sctp.connect(sctpServer.localAddress) + serverConn1 = await serverConn1Fut + serverConn2 = await serverConn2Fut + + await serverConn1.write(@[1'u8, 2, 3, 4]) + await serverConn2.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()).data == @[1'u8, 2, 3, 4] + (await clientConn2.read()).data == @[5'u8, 6, 7, 8] + (await serverConn1.read()).data == @[9'u8, 10, 11, 12] + (await serverConn2.read()).data == @[13'u8, 14, 15, 16] + await allFutures(clientConn1.close(), serverConn1.close()) + + await serverConn2.write(@[5'u8, 6, 7, 8]) + await clientConn2.write(@[13'u8, 14, 15, 16]) + check: + (await clientConn2.read()).data == @[5'u8, 6, 7, 8] + (await serverConn2.read()).data == @[13'u8, 14, 15, 16] + await allFutures(clientConn2.close(), serverConn2.close()) + + await allFutures(sctpClient1.closeSctpStack(), + sctpClient2.closeSctpStack(), + sctpServer.closeSctpStack()) From f3170707f0dd6dda687a855bb84614768c7328e8 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Tue, 27 Aug 2024 15:51:04 +0200 Subject: [PATCH 36/69] chore: move socket/error constant to sctp_utils.nim --- webrtc/sctp/sctp_transport.nim | 8 +------- webrtc/sctp/sctp_utils.nim | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 967d9e0..f24089e 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -15,12 +15,6 @@ import export chronicles const - SctpEINPROGRESS = - when defined(windows): - import winlean - winlean.WSAEINPROGRESS.cint - else: - chronos.EINPROGRESS.cint SctpTransportTracker* = "webrtc.sctp.transport" IPPROTO_SCTP = 132 @@ -197,7 +191,7 @@ proc listen*(self: Sctp, sctpPort: uint16 = 5000) = var on: int = 1 doAssert 0 == usrsctp_set_non_blocking(sock, 1) var sin: Sockaddr_in - sin.sin_family = type(sin.sin_family)(AF_INET) + sin.sin_family = type(sin.sin_family)(SctpAF_INET) sin.sin_port = htons(sctpPort) sin.sin_addr.s_addr = htonl(INADDR_ANY) doAssert 0 == diff --git a/webrtc/sctp/sctp_utils.nim b/webrtc/sctp/sctp_utils.nim index 1d92fee..59b8590 100644 --- a/webrtc/sctp/sctp_utils.nim +++ b/webrtc/sctp/sctp_utils.nim @@ -7,11 +7,17 @@ # This file may not be copied, modified, or distributed except according to # those terms. +import nativesockets, winlean import binary_serialization -proc sctpStrerror*( - error: int -): cstring {.importc: "strerror", cdecl, header: "".} +when defined(windows): + const + SctpAF_INET* = winlean.AF_INET + SctpEINPROGRESS* = winlean.WSAEINPROGRESS.cint +else: + const + SctpAF_INET* = nativesockets.AF_INET + SctpEINPROGRESS* = EINPROGRESS.cint type # These three objects are used for debugging/trace only @@ -66,6 +72,10 @@ proc getSctpPacket*(buffer: seq[byte]): SctpPacketStructure = # padding; could use `size.inc(-size %% 4)` instead but it lacks clarity size.inc(1) +proc sctpStrerror*( + error: int +): cstring {.importc: "strerror", cdecl, header: "".} + template usrsctpAwait*(self: untyped, body: untyped): untyped = # usrsctpAwait is template which set `sentFuture` to nil then calls (usually) # an usrsctp function. If during the synchronous run of the usrsctp function From 2a80d57d685fca6c0504215a0bb34d66e25c4916 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Tue, 27 Aug 2024 15:57:49 +0200 Subject: [PATCH 37/69] fix: change EINPROGRESS again --- webrtc/sctp/sctp_utils.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webrtc/sctp/sctp_utils.nim b/webrtc/sctp/sctp_utils.nim index 59b8590..a295ce6 100644 --- a/webrtc/sctp/sctp_utils.nim +++ b/webrtc/sctp/sctp_utils.nim @@ -8,7 +8,7 @@ # those terms. import nativesockets, winlean -import binary_serialization +import binary_serialization, chronos when defined(windows): const @@ -17,7 +17,7 @@ when defined(windows): else: const SctpAF_INET* = nativesockets.AF_INET - SctpEINPROGRESS* = EINPROGRESS.cint + SctpEINPROGRESS* = chronos.EINPROGRESS.cint type # These three objects are used for debugging/trace only From 33056153df6c28246c234e719aa2cd56b1bfbee8 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Wed, 28 Aug 2024 10:46:25 +0200 Subject: [PATCH 38/69] fix: multiple initialization causing issues --- webrtc/sctp/sctp_transport.nim | 54 +++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index f24089e..0093bb1 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -18,6 +18,8 @@ const SctpTransportTracker* = "webrtc.sctp.transport" IPPROTO_SCTP = 132 +var numberOfInitializedSctp = 0 + logScope: topics = "webrtc sctp" @@ -97,12 +99,36 @@ proc stopServer*(self: Sctp) = pc.sctpSocket.usrsctp_close() self.sockServer.usrsctp_close() +proc listen*(self: Sctp, sctpPort: uint16 = 5000) = + if self.isServer: + trace "Try to start the server twice" + return + self.isServer = true + trace "Listening", sctpPort + doAssert 0 == usrsctp_sysctl_set_sctp_blackhole(2) + doAssert 0 == usrsctp_sysctl_set_sctp_no_csum_on_loopback(0) + doAssert 0 == usrsctp_sysctl_set_sctp_delayed_sack_time_default(0) + let sock = usrsctp_socket(AF_CONN, SOCK_STREAM.toInt(), IPPROTO_SCTP, nil, nil, 0, nil) + var on: int = 1 + doAssert 0 == usrsctp_set_non_blocking(sock, 1) + var sin: Sockaddr_in + sin.sin_family = type(sin.sin_family)(SctpAF_INET) + sin.sin_port = htons(sctpPort) + sin.sin_addr.s_addr = htonl(INADDR_ANY) + doAssert 0 == + usrsctp_bind(sock, cast[ptr SockAddr](addr sin), SockLen(sizeof(Sockaddr_in))) + doAssert 0 >= usrsctp_listen(sock, 1) + doAssert 0 == sock.usrsctp_set_upcall(handleAccept, cast[pointer](self)) + self.sockServer = sock + proc new*(T: type Sctp, dtls: Dtls): T = var self = T() self.gotConnection = newAsyncEvent() self.dtls = dtls - usrsctp_init_nothreads(dtls.localAddress.port.uint16, sendCallback, printf) + if numberOfInitializedSctp <= 0: + usrsctp_init_nothreads(dtls.localAddress.port.uint16, sendCallback, printf) + numberOfInitializedSctp += 1 discard usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_ALL.uint32) discard usrsctp_sysctl_set_sctp_ecn_enable(1) trackCounter(SctpTransportTracker) @@ -111,7 +137,9 @@ proc new*(T: type Sctp, dtls: Dtls): T = proc close*(self: Sctp) {.async: (raises: [CancelledError]).} = # TODO: close every connections untrackCounter(SctpTransportTracker) - discard self.usrsctpAwait usrsctp_finish() + numberOfInitializedSctp -= 1 + if numberOfInitializedSctp == 0: + discard self.usrsctpAwait usrsctp_finish() proc readLoopProc(res: SctpConn) {.async: (raises: [CancelledError, WebRtcError]).} = while true: @@ -178,28 +206,6 @@ proc accept*( trackCounter(SctpConnTracker) return conn -proc listen*(self: Sctp, sctpPort: uint16 = 5000) = - if self.isServer: - trace "Try to start the server twice" - return - self.isServer = true - trace "Listening", sctpPort - doAssert 0 == usrsctp_sysctl_set_sctp_blackhole(2) - doAssert 0 == usrsctp_sysctl_set_sctp_no_csum_on_loopback(0) - doAssert 0 == usrsctp_sysctl_set_sctp_delayed_sack_time_default(0) - let sock = usrsctp_socket(AF_CONN, SOCK_STREAM.toInt(), IPPROTO_SCTP, nil, nil, 0, nil) - var on: int = 1 - doAssert 0 == usrsctp_set_non_blocking(sock, 1) - var sin: Sockaddr_in - sin.sin_family = type(sin.sin_family)(SctpAF_INET) - sin.sin_port = htons(sctpPort) - sin.sin_addr.s_addr = htonl(INADDR_ANY) - doAssert 0 == - usrsctp_bind(sock, cast[ptr SockAddr](addr sin), SockLen(sizeof(Sockaddr_in))) - doAssert 0 >= usrsctp_listen(sock, 1) - doAssert 0 == sock.usrsctp_set_upcall(handleAccept, cast[pointer](self)) - self.sockServer = sock - proc connect*( self: Sctp, raddr: TransportAddress, sctpPort: uint16 = 5000 ): Future[SctpConn] {.async: (raises: [CancelledError, WebRtcError]).} = From b97ffaf656138f8e71e111038f0a0f34df68952b Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 30 Aug 2024 12:39:39 +0200 Subject: [PATCH 39/69] fix: closing sctp connection should close underlying dtls connection --- webrtc/sctp/sctp_connection.nim | 3 ++- webrtc/sctp/sctp_transport.nim | 10 ++-------- webrtc/sctp/sctp_utils.nim | 3 ++- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 51d18ad..98eb263 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -186,8 +186,9 @@ proc write*( ) {.async: (raises: [CancelledError, WebRtcError]).} = await self.write(s.toBytes()) -proc close*(self: SctpConn) {.async: (raises: [CancelledError]).} = +proc close*(self: SctpConn) {.async: (raises: [CancelledError, WebRtcError]).} = self.usrsctpAwait: self.sctpSocket.usrsctp_close() + await self.conn.close() usrsctp_deregister_address(cast[pointer](self)) untrackCounter(SctpConnTracker) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 0093bb1..3b2dfb7 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -18,8 +18,6 @@ const SctpTransportTracker* = "webrtc.sctp.transport" IPPROTO_SCTP = 132 -var numberOfInitializedSctp = 0 - logScope: topics = "webrtc sctp" @@ -126,9 +124,7 @@ proc new*(T: type Sctp, dtls: Dtls): T = self.gotConnection = newAsyncEvent() self.dtls = dtls - if numberOfInitializedSctp <= 0: - usrsctp_init_nothreads(dtls.localAddress.port.uint16, sendCallback, printf) - numberOfInitializedSctp += 1 + usrsctp_init_nothreads(dtls.localAddress.port.uint16, sendCallback, printf) discard usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_ALL.uint32) discard usrsctp_sysctl_set_sctp_ecn_enable(1) trackCounter(SctpTransportTracker) @@ -137,9 +133,7 @@ proc new*(T: type Sctp, dtls: Dtls): T = proc close*(self: Sctp) {.async: (raises: [CancelledError]).} = # TODO: close every connections untrackCounter(SctpTransportTracker) - numberOfInitializedSctp -= 1 - if numberOfInitializedSctp == 0: - discard self.usrsctpAwait usrsctp_finish() + discard self.usrsctpAwait usrsctp_finish() proc readLoopProc(res: SctpConn) {.async: (raises: [CancelledError, WebRtcError]).} = while true: diff --git a/webrtc/sctp/sctp_utils.nim b/webrtc/sctp/sctp_utils.nim index a295ce6..cd71419 100644 --- a/webrtc/sctp/sctp_utils.nim +++ b/webrtc/sctp/sctp_utils.nim @@ -7,10 +7,11 @@ # This file may not be copied, modified, or distributed except according to # those terms. -import nativesockets, winlean +import nativesockets import binary_serialization, chronos when defined(windows): + import winlean const SctpAF_INET* = winlean.AF_INET SctpEINPROGRESS* = winlean.WSAEINPROGRESS.cint From f0ff34c781557cf269b496dc5cca5552c98af2a0 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 30 Aug 2024 13:09:18 +0200 Subject: [PATCH 40/69] fix: remove printf for windows temporarily --- webrtc/sctp/sctp_transport.nim | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 3b2dfb7..234b75f 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -124,7 +124,10 @@ proc new*(T: type Sctp, dtls: Dtls): T = self.gotConnection = newAsyncEvent() self.dtls = dtls - usrsctp_init_nothreads(dtls.localAddress.port.uint16, sendCallback, printf) + when defined(windows): + usrsctp_init_nothreads(dtls.localAddress.port.uint16, sendCallback, nil) + else: + usrsctp_init_nothreads(dtls.localAddress.port.uint16, sendCallback, printf) discard usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_ALL.uint32) discard usrsctp_sysctl_set_sctp_ecn_enable(1) trackCounter(SctpTransportTracker) From a59bb7608a3f97b0237443a54d4c074379ec1c07 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Thu, 5 Sep 2024 12:24:38 +0200 Subject: [PATCH 41/69] fix: add iphlpapi for windows --- webrtc.nimble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc.nimble b/webrtc.nimble index c0a8bbd..4583dee 100644 --- a/webrtc.nimble +++ b/webrtc.nimble @@ -24,7 +24,7 @@ var cfg = " --threads:on --opt:speed" when defined(windows): - cfg = cfg & " --clib:ws2_32" + cfg = cfg & " --clib:ws2_32 --clib:iphlpapi" import hashes From 4a80ffd3cb1d119ce7cea7bbf40d34cf16b30f88 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Thu, 5 Sep 2024 12:34:12 +0200 Subject: [PATCH 42/69] chore: change add errno to log --- webrtc/sctp/sctp_transport.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 234b75f..b59d31e 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -228,7 +228,7 @@ proc connect*( ) if connErr != 0 and errno != SctpEINPROGRESS: raise - newException(WebRtcError, "SCTP - Connection failed: " & $(sctpStrerror(errno))) + newException(WebRtcError, "SCTP - Connection failed: " & $(sctpStrerror(errno)) & $errno) conn.connectEvent.clear() await conn.connectEvent.wait() From 5f50e55cbe10982d693c493a515d76e3c027d7a7 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 6 Sep 2024 12:51:11 +0200 Subject: [PATCH 43/69] feat: replace sentFuture by sendQueue --- webrtc/sctp/sctp_connection.nim | 26 ++++++++++++++++++++++++-- webrtc/sctp/sctp_transport.nim | 10 ++++------ webrtc/sctp/sctp_utils.nim | 16 ---------------- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 98eb263..9848338 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -42,13 +42,35 @@ type readLoop*: Future[void] sctpSocket*: ptr socket dataRecv*: AsyncQueue[SctpMessage] - sentFuture*: Future[void].Raising([CancelledError]) + sendQueue: seq[byte] proc remoteAddress*(self: SctpConn): TransportAddress = if self.conn.isNil(): raise newException(WebRtcError, "SCTP - Connection not set") return self.conn.remoteAddress() +proc trySend(self: SctpConn) {.async: (raises: [CancelledError]).} = + try: + trace "Send To", address = self.remoteAddress() + await self.conn.write(self.sendQueue) + except CatchableError as exc: + trace "Send Failed", message = exc.msg + +template usrsctpAwait*(self: SctpConn, body: untyped): untyped = + # usrsctpAwait is template which set `sendQueue` to @[] then calls + # an usrsctp function. If during the synchronous run of the usrsctp function + # `sendQueue` is set, it is sent at the end of the function. + self.sendQueue = @[] + when type(body) is void: + (body) + if self.sendQueue.len() > 0: + await self.trySend() + else: + let res = (body) + if self.sendQueue.len() > 0: + await self.trySend() + res + # -- usrsctp send and receive callback -- proc recvCallback*(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = @@ -117,7 +139,7 @@ proc sendCallback*( except CatchableError as exc: trace "Send Failed", message = exc.msg - conn.sentFuture = testSend() + conn.sendQueue = buf proc toFlags(params: SctpMessageParameters): uint16 = if params.endOfRecord: diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index b59d31e..3cb9b29 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -42,7 +42,6 @@ type Sctp* = ref object isServer: bool sockServer: ptr socket pendingConnections: seq[SctpConn] - sentFuture: Future[void].Raising([CancelledError]) # -- usrsctp accept and connect callbacks -- @@ -136,7 +135,7 @@ proc new*(T: type Sctp, dtls: Dtls): T = proc close*(self: Sctp) {.async: (raises: [CancelledError]).} = # TODO: close every connections untrackCounter(SctpTransportTracker) - discard self.usrsctpAwait usrsctp_finish() + discard usrsctp_finish() proc readLoopProc(res: SctpConn) {.async: (raises: [CancelledError, WebRtcError]).} = while true: @@ -222,10 +221,9 @@ proc connect*( usrsctp_register_address(cast[pointer](conn)) conn.readLoop = conn.readLoopProc() - let connErr = self.usrsctpAwait: - conn.sctpSocket.usrsctp_connect( - cast[ptr SockAddr](addr sconn), SockLen(sizeof(sconn)) - ) + let connErr = conn.sctpSocket.usrsctp_connect( + cast[ptr SockAddr](addr sconn), SockLen(sizeof(sconn)) + ) if connErr != 0 and errno != SctpEINPROGRESS: raise newException(WebRtcError, "SCTP - Connection failed: " & $(sctpStrerror(errno)) & $errno) diff --git a/webrtc/sctp/sctp_utils.nim b/webrtc/sctp/sctp_utils.nim index cd71419..0a45f1d 100644 --- a/webrtc/sctp/sctp_utils.nim +++ b/webrtc/sctp/sctp_utils.nim @@ -76,19 +76,3 @@ proc getSctpPacket*(buffer: seq[byte]): SctpPacketStructure = proc sctpStrerror*( error: int ): cstring {.importc: "strerror", cdecl, header: "".} - -template usrsctpAwait*(self: untyped, body: untyped): untyped = - # usrsctpAwait is template which set `sentFuture` to nil then calls (usually) - # an usrsctp function. If during the synchronous run of the usrsctp function - # `sendCallback` is called, then `sentFuture` is set and waited. - # self should be Sctp or SctpConn - self.sentFuture = nil - when type(body) is void: - (body) - if self.sentFuture != nil: - await self.sentFuture - else: - let res = (body) - if self.sentFuture != nil: - await self.sentFuture - res From 374c83d7f77de0df466f3eb42bd9feb9d92ca322 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 6 Sep 2024 13:26:57 +0200 Subject: [PATCH 44/69] feat: better use of SctpState --- webrtc/sctp/sctp_connection.nim | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 9848338..3868835 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -159,14 +159,17 @@ proc new*(T: typedesc[SctpConn], conn: DtlsConn): T = proc read*(self: SctpConn): Future[SctpMessage] {.async: (raises: [CancelledError]).} = # Used by DataChannel, returns SctpMessage in order to get the stream # and protocol ids + if self.state == SctpClosed: + raise newException(WebRtcError, "Try to read on an already closed SctpConn") return await self.dataRecv.popFirst() proc write*( self: SctpConn, buf: seq[byte], sendParams = default(SctpMessageParameters) ) {.async: (raises: [CancelledError, WebRtcError]).} = # Used by DataChannel, writes buf on the Dtls connection. + if self.state == SctpClosed: + raise newException(WebRtcError, "Try to write on an already closed SctpConn") trace "Write", buf - var cpy = buf let sendvErr = if sendParams == default(SctpMessageParameters): @@ -209,8 +212,12 @@ proc write*( await self.write(s.toBytes()) proc close*(self: SctpConn) {.async: (raises: [CancelledError, WebRtcError]).} = + if self.state == SctpClosed: + debug "Try to close SctpConn twice" + return self.usrsctpAwait: self.sctpSocket.usrsctp_close() + self.state = SctpClosed await self.conn.close() usrsctp_deregister_address(cast[pointer](self)) untrackCounter(SctpConnTracker) From 0d7f15e1934bf29d642a32888d01d91b1861e367 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 6 Sep 2024 13:49:45 +0200 Subject: [PATCH 45/69] fix: read now raises WebRtcError --- webrtc/sctp/sctp_connection.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 3868835..17bdee7 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -156,7 +156,7 @@ proc new*(T: typedesc[SctpConn], conn: DtlsConn): T = dataRecv: newAsyncQueue[SctpMessage](), ) -proc read*(self: SctpConn): Future[SctpMessage] {.async: (raises: [CancelledError]).} = +proc read*(self: SctpConn): Future[SctpMessage] {.async: (raises: [CancelledError, WebRtcError]).} = # Used by DataChannel, returns SctpMessage in order to get the stream # and protocol ids if self.state == SctpClosed: From ab707d4bf743f43ea06554260d4e57fad3cbd775 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 6 Sep 2024 16:37:25 +0200 Subject: [PATCH 46/69] fix: conninput and connect are now awaited --- webrtc/sctp/sctp_connection.nim | 41 ++++++++++++++++++++++++++------- webrtc/sctp/sctp_transport.nim | 27 +--------------------- webrtc/sctp/sctp_utils.nim | 2 ++ 3 files changed, 36 insertions(+), 34 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 17bdee7..89fcf26 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -49,17 +49,17 @@ proc remoteAddress*(self: SctpConn): TransportAddress = raise newException(WebRtcError, "SCTP - Connection not set") return self.conn.remoteAddress() -proc trySend(self: SctpConn) {.async: (raises: [CancelledError]).} = - try: - trace "Send To", address = self.remoteAddress() - await self.conn.write(self.sendQueue) - except CatchableError as exc: - trace "Send Failed", message = exc.msg - template usrsctpAwait*(self: SctpConn, body: untyped): untyped = # usrsctpAwait is template which set `sendQueue` to @[] then calls # an usrsctp function. If during the synchronous run of the usrsctp function # `sendQueue` is set, it is sent at the end of the function. + proc trySend(conn: SctpConn) {.async: (raises: [CancelledError]).} = + try: + trace "Send To", address = conn.remoteAddress() + await conn.conn.write(self.sendQueue) + except CatchableError as exc: + trace "Send Failed", exceptionMsg = exc.msg + self.sendQueue = @[] when type(body) is void: (body) @@ -147,14 +147,39 @@ proc toFlags(params: SctpMessageParameters): uint16 = if params.unordered: result = result or SCTP_UNORDERED +proc readLoopProc(self: SctpConn) {.async: (raises: [CancelledError, WebRtcError]).} = + while true: + let msg = await self.conn.read() + if msg == @[]: + trace "Sctp read loop stopped, DTLS connection closed" + return + trace "Receive data", + remoteAddress = self.conn.remoteAddress(), sctPacket = $(msg.getSctpPacket()) + self.usrsctpAwait: + usrsctp_conninput(cast[pointer](self), unsafeAddr msg[0], uint(msg.len), 0) + proc new*(T: typedesc[SctpConn], conn: DtlsConn): T = - T( + result = T( conn: conn, state: SctpConnecting, connectEvent: AsyncEvent(), acceptEvent: AsyncEvent(), dataRecv: newAsyncQueue[SctpMessage](), ) + result.readLoop = result.readLoopProc() + usrsctp_register_address(cast[pointer](result)) + +proc connect*(self: SctpConn, sctpPort: uint16) {.async: (raises: [CancelledError, WebRtcError]).} = + var sconn: Sockaddr_conn + sconn.sconn_family = AF_CONN + sconn.sconn_port = htons(sctpPort) + sconn.sconn_addr = cast[pointer](self) + let connErr = self.usrsctpAwait: self.sctpSocket.usrsctp_connect( + cast[ptr SockAddr](unsafeAddr sconn), SockLen(sizeof(sconn)) + ) + if connErr != 0 and errno != SctpEINPROGRESS: + raise + newException(WebRtcError, "SCTP - Connection failed: " & $(sctpStrerror(errno)) & $errno) proc read*(self: SctpConn): Future[SctpMessage] {.async: (raises: [CancelledError, WebRtcError]).} = # Used by DataChannel, returns SctpMessage in order to get the stream diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 3cb9b29..47bdec8 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -30,7 +30,6 @@ logScope: # - Replace doAssert by a proper exception management # - Find a clean way to manage SCTP ports -var errno {.importc, header: "".}: cint ## error variable proc printf( format: cstring ) {.cdecl, importc: "printf", varargs, header: "", gcsafe.} @@ -137,16 +136,6 @@ proc close*(self: Sctp) {.async: (raises: [CancelledError]).} = untrackCounter(SctpTransportTracker) discard usrsctp_finish() -proc readLoopProc(res: SctpConn) {.async: (raises: [CancelledError, WebRtcError]).} = - while true: - let msg = await res.conn.read() - if msg == @[]: - trace "Sctp read loop stopped, DTLS connection closed" - return - trace "Receive data", - remoteAddress = res.conn.remoteAddress(), sctPacket = $(msg.getSctpPacket()) - usrsctp_conninput(cast[pointer](res), unsafeAddr msg[0], uint(msg.len), 0) - proc socketSetup( conn: SctpConn, callback: proc(a1: ptr socket, a2: pointer, a3: cint) {.cdecl.} ): bool = @@ -190,8 +179,6 @@ proc accept*( var conn: SctpConn while true: conn = SctpConn.new(await self.dtls.accept()) - usrsctp_register_address(cast[pointer](conn)) - conn.readLoop = conn.readLoopProc() conn.acceptEvent.clear() await conn.acceptEvent.wait() if conn.state == SctpState.SctpConnected and conn.socketSetup(recvCallback): @@ -214,19 +201,7 @@ proc connect*( if not conn.socketSetup(handleConnect): raise newException(WebRtcError, "SCTP - Socket setup failed while connecting") - var sconn: Sockaddr_conn - sconn.sconn_family = AF_CONN - sconn.sconn_port = htons(sctpPort) - sconn.sconn_addr = cast[pointer](conn) - usrsctp_register_address(cast[pointer](conn)) - conn.readLoop = conn.readLoopProc() - - let connErr = conn.sctpSocket.usrsctp_connect( - cast[ptr SockAddr](addr sconn), SockLen(sizeof(sconn)) - ) - if connErr != 0 and errno != SctpEINPROGRESS: - raise - newException(WebRtcError, "SCTP - Connection failed: " & $(sctpStrerror(errno)) & $errno) + await conn.connect(sctpPort) conn.connectEvent.clear() await conn.connectEvent.wait() diff --git a/webrtc/sctp/sctp_utils.nim b/webrtc/sctp/sctp_utils.nim index 0a45f1d..54869a3 100644 --- a/webrtc/sctp/sctp_utils.nim +++ b/webrtc/sctp/sctp_utils.nim @@ -10,6 +10,8 @@ import nativesockets import binary_serialization, chronos +var errno* {.importc, header: "".}: cint ## error variable + when defined(windows): import winlean const From 13e45605fe056458b5d9bae7db725533306ccb85 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 6 Sep 2024 16:57:02 +0200 Subject: [PATCH 47/69] fix: readLoop is now stop when SctpConn is closed --- webrtc/sctp/sctp_connection.nim | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 89fcf26..87168a8 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -39,7 +39,7 @@ type state*: SctpState connectEvent*: AsyncEvent acceptEvent*: AsyncEvent - readLoop*: Future[void] + readLoop: Future[void].Raising([CancelledError, WebRtcError]) sctpSocket*: ptr socket dataRecv*: AsyncQueue[SctpMessage] sendQueue: seq[byte] @@ -240,9 +240,10 @@ proc close*(self: SctpConn) {.async: (raises: [CancelledError, WebRtcError]).} = if self.state == SctpClosed: debug "Try to close SctpConn twice" return + usrsctp_deregister_address(cast[pointer](self)) self.usrsctpAwait: self.sctpSocket.usrsctp_close() + await self.readLoop.cancelAndWait() self.state = SctpClosed await self.conn.close() - usrsctp_deregister_address(cast[pointer](self)) untrackCounter(SctpConnTracker) From 1d66aace426c39e75bae263afc25691e1d81d9c9 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Tue, 10 Sep 2024 11:19:49 +0200 Subject: [PATCH 48/69] chore: remove useless code and rewrite trace/warn --- webrtc/sctp/sctp_transport.nim | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 47bdec8..2856e65 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -23,8 +23,7 @@ logScope: # Implementation of an Sctp client and server using the usrsctp library. # Usrsctp is usable as a single thread but it's not the intended way to -# use it. There's a lot of callbacks calling each other in a synchronous -# way where we want to be able to call asynchronous procedure, but cannot. +# use it. There's a lot of callbacks calling each other in a synchronous way. # TODO: # - Replace doAssert by a proper exception management @@ -35,18 +34,15 @@ proc printf( ) {.cdecl, importc: "printf", varargs, header: "", gcsafe.} type Sctp* = ref object - dtls: Dtls - connections: Table[TransportAddress, SctpConn] - gotConnection: AsyncEvent + dtls: Dtls # Underlying Dtls Transport + connections: Table[TransportAddress, SctpConn] # List of all the Sctp connections isServer: bool - sockServer: ptr socket - pendingConnections: seq[SctpConn] + sockServer: ptr socket # usrsctp socket to accept new connections # -- usrsctp accept and connect callbacks -- proc handleAccept(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = - # Callback procedure called when accepting a connection - trace "Handle Accept" + # Callback procedure called when a connection is about to be accepted. var sconn: Sockaddr_conn slen: Socklen = sizeof(Sockaddr_conn).uint32 @@ -60,6 +56,7 @@ proc handleAccept(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = warn "usrsctp_accept fails", error = sctpStrerror(errno) conn.state = SctpState.SctpClosed else: + trace "Scpt connection accepted", remoteAddress = conn.remoteAddress() conn.sctpSocket = sctpSocket conn.state = SctpState.SctpConnected conn.acceptEvent.fire() @@ -70,29 +67,24 @@ proc handleConnect(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = conn = cast[SctpConn](data) events = usrsctp_get_events(sock) - trace "Handle Connect", events, state = conn.state if conn.state == SctpState.SctpConnecting: if bitand(events, SCTP_EVENT_ERROR) != 0: - warn "Cannot connect", raddr = conn.remoteAddress() + warn "Cannot connect", remoteAddress = conn.remoteAddress() conn.state = SctpState.SctpClosed elif bitand(events, SCTP_EVENT_WRITE) != 0: conn.state = SctpState.SctpConnected - doAssert 0 == usrsctp_set_upcall(conn.sctpSocket, recvCallback, data) + if usrsctp_set_upcall(conn.sctpSocket, recvCallback, data) != 0: + warn "usrsctp_set_upcall fails while connecting", error = sctpStrerror(errno) + trace "Sctp connection connected", remoteAddress = conn.remoteAddress() conn.connectEvent.fire() else: - warn "should be connecting", currentState = conn.state - -# -- Sctp -- + warn "Should never happen", currentState = conn.state proc stopServer*(self: Sctp) = if not self.isServer: trace "Try to close a client" return self.isServer = false - let pcs = self.pendingConnections - self.pendingConnections = @[] - for pc in pcs: - pc.sctpSocket.usrsctp_close() self.sockServer.usrsctp_close() proc listen*(self: Sctp, sctpPort: uint16 = 5000) = @@ -119,7 +111,6 @@ proc listen*(self: Sctp, sctpPort: uint16 = 5000) = proc new*(T: type Sctp, dtls: Dtls): T = var self = T() - self.gotConnection = newAsyncEvent() self.dtls = dtls when defined(windows): From f231a9de8e32168da8a58637f89b2db97ff1c5b6 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Tue, 10 Sep 2024 12:06:39 +0200 Subject: [PATCH 49/69] fix: remove doAssert --- webrtc/sctp/sctp_connection.nim | 6 +-- webrtc/sctp/sctp_transport.nim | 94 ++++++++++++++++++++------------- webrtc/sctp/sctp_utils.nim | 8 +-- 3 files changed, 64 insertions(+), 44 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 87168a8..bc00d3f 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -101,7 +101,7 @@ proc recvCallback*(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = cast[ptr cint](addr flags), ) if n < 0: - trace "usrsctp_recvv", error = sctpStrerror(n) + trace "usrsctp_recvv", error = sctpStrerror() # TODO: should close return elif n > 0: @@ -179,7 +179,7 @@ proc connect*(self: SctpConn, sctpPort: uint16) {.async: (raises: [CancelledErro ) if connErr != 0 and errno != SctpEINPROGRESS: raise - newException(WebRtcError, "SCTP - Connection failed: " & $(sctpStrerror(errno)) & $errno) + newException(WebRtcError, "SCTP - Connection failed: " & sctpStrerror()) proc read*(self: SctpConn): Future[SctpMessage] {.async: (raises: [CancelledError, WebRtcError]).} = # Used by DataChannel, returns SctpMessage in order to get the stream @@ -229,7 +229,7 @@ proc write*( 0, ) if sendvErr < 0: - raise newException(WebRtcError, "SCTP - " & $(sctpStrerror(sendvErr))) + raise newException(WebRtcError, "SCTP - " & sctpStrerror()) proc write*( self: SctpConn, s: string diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 2856e65..41903a7 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -26,7 +26,6 @@ logScope: # use it. There's a lot of callbacks calling each other in a synchronous way. # TODO: -# - Replace doAssert by a proper exception management # - Find a clean way to manage SCTP ports proc printf( @@ -53,7 +52,7 @@ proc handleAccept(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = conn = cast[SctpConn](sconn.sconn_addr) if sctpSocket.isNil(): - warn "usrsctp_accept fails", error = sctpStrerror(errno) + warn "usrsctp_accept fails", error = sctpStrerror() conn.state = SctpState.SctpClosed else: trace "Scpt connection accepted", remoteAddress = conn.remoteAddress() @@ -74,40 +73,69 @@ proc handleConnect(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = elif bitand(events, SCTP_EVENT_WRITE) != 0: conn.state = SctpState.SctpConnected if usrsctp_set_upcall(conn.sctpSocket, recvCallback, data) != 0: - warn "usrsctp_set_upcall fails while connecting", error = sctpStrerror(errno) + warn "usrsctp_set_upcall fails while connecting", error = sctpStrerror() trace "Sctp connection connected", remoteAddress = conn.remoteAddress() conn.connectEvent.fire() else: warn "Should never happen", currentState = conn.state proc stopServer*(self: Sctp) = + ## Sctp Transport stop acting like a server + ## if not self.isServer: trace "Try to close a client" return self.isServer = false self.sockServer.usrsctp_close() -proc listen*(self: Sctp, sctpPort: uint16 = 5000) = - if self.isServer: - trace "Try to start the server twice" - return - self.isServer = true - trace "Listening", sctpPort - doAssert 0 == usrsctp_sysctl_set_sctp_blackhole(2) - doAssert 0 == usrsctp_sysctl_set_sctp_no_csum_on_loopback(0) - doAssert 0 == usrsctp_sysctl_set_sctp_delayed_sack_time_default(0) +proc serverSetup(self: Sctp, sctpPort: uint16): bool = + if usrsctp_sysctl_set_sctp_blackhole(2) != 0: + warn "usrsctp_sysctl_set_sctp_blackhole fails", error = sctpStrerror() + return false + + if usrsctp_sysctl_set_sctp_no_csum_on_loopback(0) != 0: + warn "usrsctp_sysctl_set_sctp_no_csum_on_loopback fails", error = sctpStrerror() + return false + + if usrsctp_sysctl_set_sctp_delayed_sack_time_default(0) != 0: + warn "usrsctp_sysctl_set_sctp_delayed_sack_time_default fails", error = sctpStrerror() + return false + let sock = usrsctp_socket(AF_CONN, SOCK_STREAM.toInt(), IPPROTO_SCTP, nil, nil, 0, nil) - var on: int = 1 - doAssert 0 == usrsctp_set_non_blocking(sock, 1) + if usrsctp_set_non_blocking(sock, 1) != 0: + warn "usrsctp_set_non_blocking fails", error = sctpStrerror() + return false + var sin: Sockaddr_in sin.sin_family = type(sin.sin_family)(SctpAF_INET) sin.sin_port = htons(sctpPort) sin.sin_addr.s_addr = htonl(INADDR_ANY) - doAssert 0 == - usrsctp_bind(sock, cast[ptr SockAddr](addr sin), SockLen(sizeof(Sockaddr_in))) - doAssert 0 >= usrsctp_listen(sock, 1) - doAssert 0 == sock.usrsctp_set_upcall(handleAccept, cast[pointer](self)) + if usrsctp_bind(sock, cast[ptr SockAddr](addr sin), SockLen(sizeof(Sockaddr_in))) != 0: + warn "usrsctp_bind fails", error = sctpStrerror() + return false + + if usrsctp_listen(sock, 1) < 0: + warn "usrsctp_listen fails", error = sctpStrerror() + return false + + if sock.usrsctp_set_upcall(handleAccept, cast[pointer](self)) != 0: + warn "usrsctp_set_upcall fails", error = sctpStrerror() + return false + self.sockServer = sock + return true + +proc listen*(self: Sctp, sctpPort: uint16 = 5000) = + ## listen marks the Sctp Transport as a transport that will be used to accept + ## incoming connection requests using accept. + ## + if self.isServer: + trace "Try to start the server twice" + return + self.isServer = true + trace "Sctp listening", sctpPort + if not self.serverSetup(sctpPort): + raise newException(WebRtcError, "SCTP - Fails to listen") proc new*(T: type Sctp, dtls: Dtls): T = var self = T() @@ -130,32 +158,22 @@ proc close*(self: Sctp) {.async: (raises: [CancelledError]).} = proc socketSetup( conn: SctpConn, callback: proc(a1: ptr socket, a2: pointer, a3: cint) {.cdecl.} ): bool = - var - errorCode = conn.sctpSocket.usrsctp_set_non_blocking(1) - nodelay: uint32 = 1 - recvinfo: uint32 = 1 - - if errorCode != 0: - warn "usrsctp_set_non_blocking fails", error = sctpStrerror(errorCode) + if conn.sctpSocket.usrsctp_set_non_blocking(1) != 0: + warn "usrsctp_set_non_blocking fails", error = sctpStrerror() return false - errorCode = conn.sctpSocket.usrsctp_set_upcall(callback, cast[pointer](conn)) - if errorCode != 0: - warn "usrsctp_set_upcall fails", error = sctpStrerror(errorCode) + if conn.sctpSocket.usrsctp_set_upcall(callback, cast[pointer](conn)) != 0: + warn "usrsctp_set_upcall fails", error = sctpStrerror() return false - errorCode = conn.sctpSocket.usrsctp_setsockopt( - IPPROTO_SCTP, SCTP_NODELAY, addr nodelay, sizeof(nodelay).SockLen - ) - if errorCode != 0: - warn "usrsctp_setsockopt nodelay fails", error = sctpStrerror(errorCode) + var nodelay: uint32 = 1 + if conn.sctpSocket.usrsctp_setsockopt(IPPROTO_SCTP, SCTP_NODELAY, addr nodelay, sizeof(nodelay).SockLen) != 0: + warn "usrsctp_setsockopt nodelay fails", error = sctpStrerror() return false - errorCode = conn.sctpSocket.usrsctp_setsockopt( - IPPROTO_SCTP, SCTP_RECVRCVINFO, addr recvinfo, sizeof(recvinfo).SockLen - ) - if errorCode != 0: - warn "usrsctp_setsockopt recvinfo fails", error = sctpStrerror(errorCode) + var recvinfo: uint32 = 1 + if conn.sctpSocket.usrsctp_setsockopt(IPPROTO_SCTP, SCTP_RECVRCVINFO, addr recvinfo, sizeof(recvinfo).SockLen) != 0: + warn "usrsctp_setsockopt recvinfo fails", error = sctpStrerror() return false return true diff --git a/webrtc/sctp/sctp_utils.nim b/webrtc/sctp/sctp_utils.nim index 54869a3..87710cc 100644 --- a/webrtc/sctp/sctp_utils.nim +++ b/webrtc/sctp/sctp_utils.nim @@ -75,6 +75,8 @@ proc getSctpPacket*(buffer: seq[byte]): SctpPacketStructure = # padding; could use `size.inc(-size %% 4)` instead but it lacks clarity size.inc(1) -proc sctpStrerror*( - error: int -): cstring {.importc: "strerror", cdecl, header: "".} +proc sctpStrerror*(): string = + proc strerror( + error: int + ): cstring {.importc: "strerror", cdecl, header: "".} + return $(sctpStrerror(errno)) From 3647e111e6411c693b216f6c82b4d91d10a76fa5 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Tue, 10 Sep 2024 12:25:53 +0200 Subject: [PATCH 50/69] fix: renaming close into stop and change logs --- tests/testsctp.nim | 2 +- webrtc/sctp/sctp_transport.nim | 40 ++++++++++++++++++++-------------- webrtc/sctp/sctp_utils.nim | 2 +- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/tests/testsctp.nim b/tests/testsctp.nim index e659021..7d381f2 100644 --- a/tests/testsctp.nim +++ b/tests/testsctp.nim @@ -38,7 +38,7 @@ suite "SCTP": result.sctp.listen() proc closeSctpStack(self: SctpStackForTest) {.async: (raises: [CancelledError]).} = - await self.sctp.close() + await self.sctp.stop() await self.dtls.stop() await self.stun.stop() await self.udp.close() diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 41903a7..576e2c1 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -52,7 +52,7 @@ proc handleAccept(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = conn = cast[SctpConn](sconn.sconn_addr) if sctpSocket.isNil(): - warn "usrsctp_accept fails", error = sctpStrerror() + warn "usrsctp_accept failed", error = sctpStrerror() conn.state = SctpState.SctpClosed else: trace "Scpt connection accepted", remoteAddress = conn.remoteAddress() @@ -89,21 +89,23 @@ proc stopServer*(self: Sctp) = self.sockServer.usrsctp_close() proc serverSetup(self: Sctp, sctpPort: uint16): bool = + # This procedure setup usrsctp to be in "server mode" and + # creates an sctp socket on which we will listen if usrsctp_sysctl_set_sctp_blackhole(2) != 0: - warn "usrsctp_sysctl_set_sctp_blackhole fails", error = sctpStrerror() + warn "usrsctp_sysctl_set_sctp_blackhole failed", error = sctpStrerror() return false if usrsctp_sysctl_set_sctp_no_csum_on_loopback(0) != 0: - warn "usrsctp_sysctl_set_sctp_no_csum_on_loopback fails", error = sctpStrerror() + warn "usrsctp_sysctl_set_sctp_no_csum_on_loopback failed", error = sctpStrerror() return false if usrsctp_sysctl_set_sctp_delayed_sack_time_default(0) != 0: - warn "usrsctp_sysctl_set_sctp_delayed_sack_time_default fails", error = sctpStrerror() + warn "usrsctp_sysctl_set_sctp_delayed_sack_time_default failed", error = sctpStrerror() return false let sock = usrsctp_socket(AF_CONN, SOCK_STREAM.toInt(), IPPROTO_SCTP, nil, nil, 0, nil) if usrsctp_set_non_blocking(sock, 1) != 0: - warn "usrsctp_set_non_blocking fails", error = sctpStrerror() + warn "usrsctp_set_non_blocking failed", error = sctpStrerror() return false var sin: Sockaddr_in @@ -111,22 +113,22 @@ proc serverSetup(self: Sctp, sctpPort: uint16): bool = sin.sin_port = htons(sctpPort) sin.sin_addr.s_addr = htonl(INADDR_ANY) if usrsctp_bind(sock, cast[ptr SockAddr](addr sin), SockLen(sizeof(Sockaddr_in))) != 0: - warn "usrsctp_bind fails", error = sctpStrerror() + warn "usrsctp_bind failed", error = sctpStrerror() return false if usrsctp_listen(sock, 1) < 0: - warn "usrsctp_listen fails", error = sctpStrerror() + warn "usrsctp_listen failed", error = sctpStrerror() return false if sock.usrsctp_set_upcall(handleAccept, cast[pointer](self)) != 0: - warn "usrsctp_set_upcall fails", error = sctpStrerror() + warn "usrsctp_set_upcall failed", error = sctpStrerror() return false self.sockServer = sock return true proc listen*(self: Sctp, sctpPort: uint16 = 5000) = - ## listen marks the Sctp Transport as a transport that will be used to accept + ## `listen` marks the Sctp Transport as a transport that will be used to accept ## incoming connection requests using accept. ## if self.isServer: @@ -138,6 +140,8 @@ proc listen*(self: Sctp, sctpPort: uint16 = 5000) = raise newException(WebRtcError, "SCTP - Fails to listen") proc new*(T: type Sctp, dtls: Dtls): T = + ## Creates a new Sctp Transport + ## var self = T() self.dtls = dtls @@ -145,12 +149,16 @@ proc new*(T: type Sctp, dtls: Dtls): T = usrsctp_init_nothreads(dtls.localAddress.port.uint16, sendCallback, nil) else: usrsctp_init_nothreads(dtls.localAddress.port.uint16, sendCallback, printf) - discard usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_ALL.uint32) - discard usrsctp_sysctl_set_sctp_ecn_enable(1) + if usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_ALL.uint32) != 0: + # Enabling debug is not critical, doesn't matter if it fails + trace "usrsctp_sysctl_set_sctp_debug_on failed", error = sctpStrerror() + if usrsctp_sysctl_set_sctp_ecn_enable(1) != 0: + # In the same way, enabling explicit congestion notification isn't required + trace "usrsctp_sysctl_set_sctp_ecn_enable failed", error = sctpStrerror() trackCounter(SctpTransportTracker) return self -proc close*(self: Sctp) {.async: (raises: [CancelledError]).} = +proc stop*(self: Sctp) {.async: (raises: [CancelledError]).} = # TODO: close every connections untrackCounter(SctpTransportTracker) discard usrsctp_finish() @@ -159,21 +167,21 @@ proc socketSetup( conn: SctpConn, callback: proc(a1: ptr socket, a2: pointer, a3: cint) {.cdecl.} ): bool = if conn.sctpSocket.usrsctp_set_non_blocking(1) != 0: - warn "usrsctp_set_non_blocking fails", error = sctpStrerror() + warn "usrsctp_set_non_blocking failed", error = sctpStrerror() return false if conn.sctpSocket.usrsctp_set_upcall(callback, cast[pointer](conn)) != 0: - warn "usrsctp_set_upcall fails", error = sctpStrerror() + warn "usrsctp_set_upcall failed", error = sctpStrerror() return false var nodelay: uint32 = 1 if conn.sctpSocket.usrsctp_setsockopt(IPPROTO_SCTP, SCTP_NODELAY, addr nodelay, sizeof(nodelay).SockLen) != 0: - warn "usrsctp_setsockopt nodelay fails", error = sctpStrerror() + warn "usrsctp_setsockopt nodelay failed", error = sctpStrerror() return false var recvinfo: uint32 = 1 if conn.sctpSocket.usrsctp_setsockopt(IPPROTO_SCTP, SCTP_RECVRCVINFO, addr recvinfo, sizeof(recvinfo).SockLen) != 0: - warn "usrsctp_setsockopt recvinfo fails", error = sctpStrerror() + warn "usrsctp_setsockopt recvinfo failed", error = sctpStrerror() return false return true diff --git a/webrtc/sctp/sctp_utils.nim b/webrtc/sctp/sctp_utils.nim index 87710cc..b21fae5 100644 --- a/webrtc/sctp/sctp_utils.nim +++ b/webrtc/sctp/sctp_utils.nim @@ -79,4 +79,4 @@ proc sctpStrerror*(): string = proc strerror( error: int ): cstring {.importc: "strerror", cdecl, header: "".} - return $(sctpStrerror(errno)) + return $(strerror(errno)) From baf91b9aca6eb9d93eaa4f87c41f5e608d17bd6e Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Tue, 10 Sep 2024 12:37:55 +0200 Subject: [PATCH 51/69] chore: move `toFlags` to a more relevant place --- webrtc/sctp/sctp_connection.nim | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index bc00d3f..2b7af23 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -141,12 +141,6 @@ proc sendCallback*( conn.sendQueue = buf -proc toFlags(params: SctpMessageParameters): uint16 = - if params.endOfRecord: - result = result or SCTP_EOR - if params.unordered: - result = result or SCTP_UNORDERED - proc readLoopProc(self: SctpConn) {.async: (raises: [CancelledError, WebRtcError]).} = while true: let msg = await self.conn.read() @@ -188,6 +182,12 @@ proc read*(self: SctpConn): Future[SctpMessage] {.async: (raises: [CancelledErro raise newException(WebRtcError, "Try to read on an already closed SctpConn") return await self.dataRecv.popFirst() +proc toFlags(params: SctpMessageParameters): uint16 = + if params.endOfRecord: + result = result or SCTP_EOR + if params.unordered: + result = result or SCTP_UNORDERED + proc write*( self: SctpConn, buf: seq[byte], sendParams = default(SctpMessageParameters) ) {.async: (raises: [CancelledError, WebRtcError]).} = @@ -215,7 +215,7 @@ proc write*( var sendInfo = sctp_sndinfo( snd_sid: sendParams.streamId, snd_ppid: sendParams.protocolId.swapBytes(), - snd_flags: sendParams.toFlags, + snd_flags: sendParams.toFlags(), ) self.usrsctpAwait: self.sctpSocket.usrsctp_sendv( From 413f3230659ad3d5b28849b0835fabdc49733373 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Tue, 10 Sep 2024 12:50:02 +0200 Subject: [PATCH 52/69] feat: close every connection when stopping transport --- webrtc/sctp/sctp_connection.nim | 13 ++++++++++++- webrtc/sctp/sctp_transport.nim | 21 ++++++++++++++++----- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 2b7af23..f57dcbc 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -18,6 +18,8 @@ const SctpConnTracker* = "webrtc.sctp.conn" # TODO: closing connection if usrsctp_recv / usrsctp_read fails type + SctpConnOnClose* = proc() {.raises: [], gcsafe.} + SctpState* = enum SctpConnecting SctpConnected @@ -37,6 +39,7 @@ type SctpConn* = ref object conn*: DtlsConn state*: SctpState + onClose: seq[SctpConnOnClose] connectEvent*: AsyncEvent acceptEvent*: AsyncEvent readLoop: Future[void].Raising([CancelledError, WebRtcError]) @@ -141,6 +144,11 @@ proc sendCallback*( conn.sendQueue = buf +proc addOnClose*(self: SctpConn, onCloseProc: SctpConnOnClose) = + ## Adds a proc to be called when SctpConn is closed + ## + self.onClose.add(onCloseProc) + proc readLoopProc(self: SctpConn) {.async: (raises: [CancelledError, WebRtcError]).} = while true: let msg = await self.conn.read() @@ -245,5 +253,8 @@ proc close*(self: SctpConn) {.async: (raises: [CancelledError, WebRtcError]).} = self.sctpSocket.usrsctp_close() await self.readLoop.cancelAndWait() self.state = SctpClosed - await self.conn.close() untrackCounter(SctpConnTracker) + await self.conn.close() + for onCloseProc in self.onClose: + onCloseProc() + self.onClose = @[] diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 576e2c1..5273a03 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -7,7 +7,7 @@ # This file may not be copied, modified, or distributed except according to # those terms. -import tables, bitops, nativesockets, strutils +import tables, bitops, nativesockets, strutils, sequtils import usrsctp, chronos, chronicles import ./[sctp_connection, sctp_utils], ../errors, ../dtls/[dtls_transport, dtls_connection] @@ -159,9 +159,13 @@ proc new*(T: type Sctp, dtls: Dtls): T = return self proc stop*(self: Sctp) {.async: (raises: [CancelledError]).} = - # TODO: close every connections + if self.isServer: + self.stopServer() untrackCounter(SctpTransportTracker) - discard usrsctp_finish() + let connections = toSeq(self.connections.values()) + await allFutures(connections.mapIt(it.close())) + if usrsctp_finish() != 0: + warn "usrsct_finish failed", error = sctpStrerror() proc socketSetup( conn: SctpConn, callback: proc(a1: ptr socket, a2: pointer, a3: cint) {.cdecl.} @@ -185,6 +189,13 @@ proc socketSetup( return false return true +proc addConnToTable(self: Sctp, conn: SctpConn) = + let remoteAddress = conn.remoteAddress() + proc cleanup() = + self.connections.del(remoteAddress) + self.connections[remoteAddress] = conn + conn.addOnClose(cleanup) + proc accept*( self: Sctp ): Future[SctpConn] {.async: (raises: [CancelledError, WebRtcError]).} = @@ -202,7 +213,7 @@ proc accept*( break await conn.close() - self.connections[conn.remoteAddress()] = conn + self.addConnToTable(conn) trackCounter(SctpConnTracker) return conn @@ -224,6 +235,6 @@ proc connect*( await conn.connectEvent.wait() if conn.state == SctpState.SctpClosed: raise newException(WebRtcError, "SCTP - Connection failed") - self.connections[raddr] = conn + self.addConnToTable(conn) trackCounter(SctpConnTracker) return conn From 857e5da04f74a0f529ca4143cb529445cf00804b Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Thu, 12 Sep 2024 11:23:01 +0200 Subject: [PATCH 53/69] chore: remove exposed attributes --- webrtc/sctp/sctp_connection.nim | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index f57dcbc..7740978 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -37,14 +37,14 @@ type params*: SctpMessageParameters SctpConn* = ref object - conn*: DtlsConn + conn: DtlsConn state*: SctpState onClose: seq[SctpConnOnClose] connectEvent*: AsyncEvent acceptEvent*: AsyncEvent readLoop: Future[void].Raising([CancelledError, WebRtcError]) sctpSocket*: ptr socket - dataRecv*: AsyncQueue[SctpMessage] + dataRecv: AsyncQueue[SctpMessage] sendQueue: seq[byte] proc remoteAddress*(self: SctpConn): TransportAddress = @@ -77,8 +77,8 @@ template usrsctpAwait*(self: SctpConn, body: untyped): untyped = # -- usrsctp send and receive callback -- proc recvCallback*(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = - # Callback procedure called when we receive data after - # connection has been established. + # Callback procedure called when we receive data after a connection + # has been established. let conn = cast[SctpConn](data) events = usrsctp_get_events(sock) From 21e0cfe94b319cd4ced1a7b77587bd06ee8b099f Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Thu, 12 Sep 2024 11:23:53 +0200 Subject: [PATCH 54/69] chore: add description and remove trace --- webrtc/sctp/sctp_transport.nim | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 5273a03..d00b0f9 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -159,6 +159,8 @@ proc new*(T: type Sctp, dtls: Dtls): T = return self proc stop*(self: Sctp) {.async: (raises: [CancelledError]).} = + ## Stops the Sctp Transport + ## if self.isServer: self.stopServer() untrackCounter(SctpTransportTracker) @@ -170,6 +172,8 @@ proc stop*(self: Sctp) {.async: (raises: [CancelledError]).} = proc socketSetup( conn: SctpConn, callback: proc(a1: ptr socket, a2: pointer, a3: cint) {.cdecl.} ): bool = + # This procedure setup SctpConn. It should be in `sctp_connection.nim` file but I + # prefer not to expose it. if conn.sctpSocket.usrsctp_set_non_blocking(1) != 0: warn "usrsctp_set_non_blocking failed", error = sctpStrerror() return false @@ -203,7 +207,6 @@ proc accept*( ## if not self.isServer: raise newException(WebRtcError, "SCTP - Not a server") - trace "Accept connection" var conn: SctpConn while true: conn = SctpConn.new(await self.dtls.accept()) @@ -220,7 +223,8 @@ proc accept*( proc connect*( self: Sctp, raddr: TransportAddress, sctpPort: uint16 = 5000 ): Future[SctpConn] {.async: (raises: [CancelledError, WebRtcError]).} = - trace "Create Connection", raddr + ## Connect to a remote address and returns an Sctp Connection + ## let conn = SctpConn.new(await self.dtls.connect(raddr)) conn.state = SctpState.SctpConnecting conn.sctpSocket = From 185642b134aa1f5be46bfbb079da1f6ffe01a5d6 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Thu, 12 Sep 2024 11:37:14 +0200 Subject: [PATCH 55/69] chore: remove comments & exposition --- webrtc/sctp/sctp_connection.nim | 4 +--- webrtc/sctp/sctp_transport.nim | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 7740978..6883021 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -16,7 +16,6 @@ logScope: const SctpConnTracker* = "webrtc.sctp.conn" -# TODO: closing connection if usrsctp_recv / usrsctp_read fails type SctpConnOnClose* = proc() {.raises: [], gcsafe.} @@ -52,7 +51,7 @@ proc remoteAddress*(self: SctpConn): TransportAddress = raise newException(WebRtcError, "SCTP - Connection not set") return self.conn.remoteAddress() -template usrsctpAwait*(self: SctpConn, body: untyped): untyped = +template usrsctpAwait(self: SctpConn, body: untyped): untyped = # usrsctpAwait is template which set `sendQueue` to @[] then calls # an usrsctp function. If during the synchronous run of the usrsctp function # `sendQueue` is set, it is sent at the end of the function. @@ -105,7 +104,6 @@ proc recvCallback*(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = ) if n < 0: trace "usrsctp_recvv", error = sctpStrerror() - # TODO: should close return elif n > 0: # It might be necessary to check if infotype == SCTP_RECVV_RCVINFO diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index d00b0f9..b34a2f3 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -36,7 +36,7 @@ type Sctp* = ref object dtls: Dtls # Underlying Dtls Transport connections: Table[TransportAddress, SctpConn] # List of all the Sctp connections isServer: bool - sockServer: ptr socket # usrsctp socket to accept new connections + sockServer: ptr socket # usrsctp "server" socket to accept new connections # -- usrsctp accept and connect callbacks -- From 9c6b197eeb679c49f5ecb69e41c4ccd9dc596b2e Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Thu, 12 Sep 2024 11:40:16 +0200 Subject: [PATCH 56/69] chore: update logs --- webrtc/sctp/sctp_connection.nim | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 6883021..6e2d8b1 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -103,7 +103,7 @@ proc recvCallback*(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = cast[ptr cint](addr flags), ) if n < 0: - trace "usrsctp_recvv", error = sctpStrerror() + warn "usrsctp_recvv", error = sctpStrerror() return elif n > 0: # It might be necessary to check if infotype == SCTP_RECVV_RCVINFO @@ -200,7 +200,6 @@ proc write*( # Used by DataChannel, writes buf on the Dtls connection. if self.state == SctpClosed: raise newException(WebRtcError, "Try to write on an already closed SctpConn") - trace "Write", buf var cpy = buf let sendvErr = if sendParams == default(SctpMessageParameters): From 38d40f84c51ebf62b18310f3bec86db7097016db Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Thu, 12 Sep 2024 11:43:14 +0200 Subject: [PATCH 57/69] chore: remove outdated comments --- webrtc/sctp/sctp_transport.nim | 3 --- 1 file changed, 3 deletions(-) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index b34a2f3..bafee5d 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -25,9 +25,6 @@ logScope: # Usrsctp is usable as a single thread but it's not the intended way to # use it. There's a lot of callbacks calling each other in a synchronous way. -# TODO: -# - Find a clean way to manage SCTP ports - proc printf( format: cstring ) {.cdecl, importc: "printf", varargs, header: "", gcsafe.} From 8d26ed6edce0facd88b46be697bc5ac4eb77d965 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Thu, 12 Sep 2024 11:56:46 +0200 Subject: [PATCH 58/69] chore: update comment --- webrtc/sctp/sctp_transport.nim | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index bafee5d..5339f18 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -22,8 +22,9 @@ logScope: topics = "webrtc sctp" # Implementation of an Sctp client and server using the usrsctp library. -# Usrsctp is usable as a single thread but it's not the intended way to -# use it. There's a lot of callbacks calling each other in a synchronous way. +# Usrsctp is usable with a single thread but this is not the intended +# way to use it. As a result, there are many callbacks that calls each +# other synchronously. proc printf( format: cstring From a37f9df88c355e04b4bcd767c65837b66c68b204 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 20 Sep 2024 13:43:13 +0200 Subject: [PATCH 59/69] chore: remove unsafeAddr --- webrtc/sctp/sctp_connection.nim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 6e2d8b1..6840500 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -149,14 +149,14 @@ proc addOnClose*(self: SctpConn, onCloseProc: SctpConnOnClose) = proc readLoopProc(self: SctpConn) {.async: (raises: [CancelledError, WebRtcError]).} = while true: - let msg = await self.conn.read() + var msg = await self.conn.read() if msg == @[]: trace "Sctp read loop stopped, DTLS connection closed" return trace "Receive data", remoteAddress = self.conn.remoteAddress(), sctPacket = $(msg.getSctpPacket()) self.usrsctpAwait: - usrsctp_conninput(cast[pointer](self), unsafeAddr msg[0], uint(msg.len), 0) + usrsctp_conninput(cast[pointer](self), addr msg[0], uint(msg.len), 0) proc new*(T: typedesc[SctpConn], conn: DtlsConn): T = result = T( @@ -175,7 +175,7 @@ proc connect*(self: SctpConn, sctpPort: uint16) {.async: (raises: [CancelledErro sconn.sconn_port = htons(sctpPort) sconn.sconn_addr = cast[pointer](self) let connErr = self.usrsctpAwait: self.sctpSocket.usrsctp_connect( - cast[ptr SockAddr](unsafeAddr sconn), SockLen(sizeof(sconn)) + cast[ptr SockAddr](addr sconn), SockLen(sizeof(sconn)) ) if connErr != 0 and errno != SctpEINPROGRESS: raise From f41f2558bb82a42a29dd58619ae5c097b021642f Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 20 Sep 2024 13:46:47 +0200 Subject: [PATCH 60/69] fix: macos error --- webrtc/sctp/sctp_connection.nim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 6840500..22eb180 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -171,6 +171,8 @@ proc new*(T: typedesc[SctpConn], conn: DtlsConn): T = proc connect*(self: SctpConn, sctpPort: uint16) {.async: (raises: [CancelledError, WebRtcError]).} = var sconn: Sockaddr_conn + when compiles(sconn.sconn_len): + sconn.sconn_len = sizeof(sconn).uint8 sconn.sconn_family = AF_CONN sconn.sconn_port = htons(sctpPort) sconn.sconn_addr = cast[pointer](self) From ae7d377eea8bf95ebf6c175a07529bd686467783 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 4 Oct 2024 09:41:56 +0200 Subject: [PATCH 61/69] fix: use port 0 for tests --- tests/testsctp.nim | 16 ++++++++-------- webrtc/stun/stun_connection.nim | 2 +- webrtc/stun/stun_transport.nim | 2 +- webrtc/udp_transport.nim | 8 ++++++-- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/tests/testsctp.nim b/tests/testsctp.nim index 7d381f2..448714f 100644 --- a/tests/testsctp.nim +++ b/tests/testsctp.nim @@ -29,9 +29,9 @@ suite "SCTP": dtls: Dtls sctp: Sctp - proc initSctpStack(localAddress: TransportAddress): SctpStackForTest = - result.localAddress = localAddress - result.udp = UdpTransport.new(result.localAddress) + proc initSctpStack(la: TransportAddress): SctpStackForTest = + result.udp = UdpTransport.new(la) + result.localAddress = result.udp.localAddress() result.stun = Stun.new(result.udp) result.dtls = Dtls.new(result.stun) result.sctp = Sctp.new(result.dtls) @@ -45,8 +45,8 @@ suite "SCTP": asyncTest "Two SCTP nodes connecting to each other, then sending/receiving data": var - sctpServer = initSctpStack(initTAddress("127.0.0.1:4444")) - sctpClient = initSctpStack(initTAddress("127.0.0.1:5555")) + sctpServer = initSctpStack(initTAddress("127.0.0.1:0")) + sctpClient = initSctpStack(initTAddress("127.0.0.1:0")) let serverConnFut = sctpServer.sctp.accept() clientConn = await sctpClient.sctp.connect(sctpServer.localAddress) @@ -68,9 +68,9 @@ suite "SCTP": asyncTest "Two DTLS nodes connecting to the same DTLS server, sending/receiving data": var - sctpServer = initSctpStack(initTAddress("127.0.0.1:4444")) - sctpClient1 = initSctpStack(initTAddress("127.0.0.1:5555")) - sctpClient2 = initSctpStack(initTAddress("127.0.0.1:6666")) + sctpServer = initSctpStack(initTAddress("127.0.0.1:0")) + sctpClient1 = initSctpStack(initTAddress("127.0.0.1:0")) + sctpClient2 = initSctpStack(initTAddress("127.0.0.1:0")) let serverConn1Fut = sctpServer.sctp.accept() serverConn2Fut = sctpServer.sctp.accept() diff --git a/webrtc/stun/stun_connection.nim b/webrtc/stun/stun_connection.nim index 9d661a2..d732041 100644 --- a/webrtc/stun/stun_connection.nim +++ b/webrtc/stun/stun_connection.nim @@ -201,7 +201,7 @@ proc new*( ## var self = T( udp: udp, - laddr: udp.laddr, + laddr: udp.localAddress(), raddr: raddr, closed: false, dataRecv: newAsyncQueue[seq[byte]](StunMaxQueuingMessages), diff --git a/webrtc/stun/stun_transport.nim b/webrtc/stun/stun_transport.nim index becc593..3d52f53 100644 --- a/webrtc/stun/stun_transport.nim +++ b/webrtc/stun/stun_transport.nim @@ -108,7 +108,7 @@ proc new*( ## var self = T( udp: udp, - laddr: udp.laddr, + laddr: udp.localAddress(), usernameProvider: usernameProvider, usernameChecker: usernameChecker, passwordProvider: passwordProvider, diff --git a/webrtc/udp_transport.nim b/webrtc/udp_transport.nim index e458a5e..1f1625c 100644 --- a/webrtc/udp_transport.nim +++ b/webrtc/udp_transport.nim @@ -23,7 +23,7 @@ type raddr: TransportAddress UdpTransport* = ref object - laddr*: TransportAddress + laddr: TransportAddress udp: DatagramTransport dataRecv: AsyncQueue[UdpPacketInfo] closed: bool @@ -33,7 +33,7 @@ const UdpTransportTrackerName* = "webrtc.udp.transport" proc new*(T: type UdpTransport, laddr: TransportAddress): T = ## Initialize an Udp Transport ## - var self = T(laddr: laddr, closed: false) + var self = T(closed: false) proc onReceive( udp: DatagramTransport, @@ -49,6 +49,7 @@ proc new*(T: type UdpTransport, laddr: TransportAddress): T = self.dataRecv = newAsyncQueue[UdpPacketInfo]() self.udp = newDatagramTransport(onReceive, local = laddr) + self.laddr = self.udp.localAddress() trackCounter(UdpTransportTrackerName) return self @@ -87,3 +88,6 @@ proc read*(self: UdpTransport): Future[UdpPacketInfo] {.async: (raises: [Cancell return trace "UDP read" return await self.dataRecv.popFirst() + +proc localAddress*(self: UdpTransport): TransportAddress = + self.laddr From 4f8f901f3d625d28a042a562710a6627f99508b9 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 4 Oct 2024 09:51:28 +0200 Subject: [PATCH 62/69] fix: split test and build examples in ci --- .github/workflows/ci.yml | 4 ++++ webrtc.nimble | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b0276a2..07989cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,3 +71,7 @@ jobs: nim --version nimble --version nimble test + + - name: Build examples + run: | + nimble build_examples diff --git a/webrtc.nimble b/webrtc.nimble index 4583dee..5b82bea 100644 --- a/webrtc.nimble +++ b/webrtc.nimble @@ -46,8 +46,7 @@ proc runTest(filename: string) = task test, "Run the test suite": runTest("runalltests") - exec "nimble build_example" -task build_example, "Build the examples": +task build_examples, "Build the examples": buildExample("ping") buildExample("pong") From 7d612aadec8ca8cb3baf0c718aff58fe0229c8ab Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 4 Oct 2024 10:07:23 +0200 Subject: [PATCH 63/69] chore: move logutils from sctp_utils into its own file --- webrtc/sctp/sctp_logutils.nim | 69 +++++++++++++++++++++++++++++++++++ webrtc/sctp/sctp_utils.nim | 55 +--------------------------- 2 files changed, 70 insertions(+), 54 deletions(-) create mode 100644 webrtc/sctp/sctp_logutils.nim diff --git a/webrtc/sctp/sctp_logutils.nim b/webrtc/sctp/sctp_logutils.nim new file mode 100644 index 0000000..f75452c --- /dev/null +++ b/webrtc/sctp/sctp_logutils.nim @@ -0,0 +1,69 @@ +# 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 binary_serialization + +# This file defines custom objects and procedures to improve the +# readability and accuracy of logging SCTP messages. The default +# usrsctp logger may not provide sufficient detail or clarity for +# SCTP message analysis, so this implementation creates more structured +# and informative logs. By parsing and formatting SCTP packet headers +# and chunks into human-readable strings, it provides clearer insights +# into the data being transmitted. This aids debugging by offering a +# more descriptive view of SCTP traffic than what is available +# by default. + +type + SctpChunk* = object + chunkType*: uint8 + flag*: uint8 + length* {.bin_value: it.data.len() + 4.}: uint16 + data* {.bin_len: it.length - 4.}: seq[byte] + + SctpPacketHeader* = object + srcPort*: uint16 + dstPort*: uint16 + verifTag*: uint32 + checksum*: uint32 + + SctpPacketStructure* = object + header*: SctpPacketHeader + chunks*: seq[SctpChunk] + +proc dataToString(data: seq[byte]): string = + if data.len() < 8: + return $data + result = "@[" + result &= $data[0] & ", " & $data[1] & ", " & $data[2] & ", " & $data[3] & " ... " + result &= $data[^4] & ", " & $data[^3] & ", " & $data[^2] & ", " & $data[^1] & "]" + +proc `$`*(packet: SctpPacketStructure): string = + result = "{header: {srcPort: " + result &= $(packet.header.srcPort) & ", dstPort: " + result &= $(packet.header.dstPort) & "}, chunks: @[" + var counter = 0 + for chunk in packet.chunks: + result &= "{type: " & $(chunk.chunkType) & ", len: " + result &= $(chunk.length) & ", data: " + result &= chunk.data.dataToString() + counter += 1 + if counter < packet.chunks.len(): + result &= ", " + result &= "]}" + +proc getSctpPacket*(buffer: seq[byte]): SctpPacketStructure = + result.header = Binary.decode(buffer, SctpPacketHeader) + var size = sizeof(SctpPacketHeader) + while size < buffer.len: + let chunk = Binary.decode(buffer[size ..^ 1], SctpChunk) + result.chunks.add(chunk) + size.inc(chunk.length.int) + while size mod 4 != 0: + # padding; could use `size.inc(-size %% 4)` instead but it lacks clarity + size.inc(1) diff --git a/webrtc/sctp/sctp_utils.nim b/webrtc/sctp/sctp_utils.nim index b21fae5..17a5fca 100644 --- a/webrtc/sctp/sctp_utils.nim +++ b/webrtc/sctp/sctp_utils.nim @@ -8,7 +8,7 @@ # those terms. import nativesockets -import binary_serialization, chronos +import chronos var errno* {.importc, header: "".}: cint ## error variable @@ -22,59 +22,6 @@ else: SctpAF_INET* = nativesockets.AF_INET SctpEINPROGRESS* = chronos.EINPROGRESS.cint -type - # These three objects are used for debugging/trace only - SctpChunk* = object - chunkType*: uint8 - flag*: uint8 - length* {.bin_value: it.data.len() + 4.}: uint16 - data* {.bin_len: it.length - 4.}: seq[byte] - - SctpPacketHeader* = object - srcPort*: uint16 - dstPort*: uint16 - verifTag*: uint32 - checksum*: uint32 - - SctpPacketStructure* = object - header*: SctpPacketHeader - chunks*: seq[SctpChunk] - -proc dataToString(data: seq[byte]): string = - # Only used for debugging/trace - if data.len() < 8: - return $data - result = "@[" - result &= $data[0] & ", " & $data[1] & ", " & $data[2] & ", " & $data[3] & " ... " - result &= $data[^4] & ", " & $data[^3] & ", " & $data[^2] & ", " & $data[^1] & "]" - -proc `$`*(packet: SctpPacketStructure): string = - # Only used for debugging/trace - result = "{header: {srcPort: " - result &= $(packet.header.srcPort) & ", dstPort: " - result &= $(packet.header.dstPort) & "}, chunks: @[" - var counter = 0 - for chunk in packet.chunks: - result &= "{type: " & $(chunk.chunkType) & ", len: " - result &= $(chunk.length) & ", data: " - result &= chunk.data.dataToString() - counter += 1 - if counter < packet.chunks.len(): - result &= ", " - result &= "]}" - -proc getSctpPacket*(buffer: seq[byte]): SctpPacketStructure = - # Only used for debugging/trace - result.header = Binary.decode(buffer, SctpPacketHeader) - var size = sizeof(SctpPacketHeader) - while size < buffer.len: - let chunk = Binary.decode(buffer[size ..^ 1], SctpChunk) - result.chunks.add(chunk) - size.inc(chunk.length.int) - while size mod 4 != 0: - # padding; could use `size.inc(-size %% 4)` instead but it lacks clarity - size.inc(1) - proc sctpStrerror*(): string = proc strerror( error: int From 5adb9aed8879932c4fe1c5ecc9f75a359a1795d2 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 4 Oct 2024 10:32:41 +0200 Subject: [PATCH 64/69] docs: `SctpConn` and `SctpMessageParameters` field --- webrtc/sctp/sctp_connection.nim | 37 +++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 22eb180..e70258f 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -25,10 +25,24 @@ type SctpClosed SctpMessageParameters* = object + # This object is used to help manage messages exchanged over SCTP + # within the DataChannel stack. protocolId*: uint32 + # protocolId is used to distinguish different protocols within + # SCTP stream. In WebRTC, this is used to define the type of application + # data being transferred (text data, binary data...). streamId*: uint16 + # streamId identifies the specific SCTP stream. In WebRTC, each + # DataChannel corresponds to a different stream, so the streamId is + # used to map the message to the appropriate DataChannel. endOfRecord*: bool + # endOfRecord indicates whether the current SCTP message is the + # final part of a record or not. This is related to the + # fragmentation and reassembly of messages. unordered*: bool + # The unordered flag determines whether the message should be + # delivered in order or not. SCTP allows for both ordered and + # unordered delivery of messages. SctpMessage* = ref object data*: seq[byte] @@ -36,15 +50,21 @@ type params*: SctpMessageParameters SctpConn* = ref object - conn: DtlsConn - state*: SctpState - onClose: seq[SctpConnOnClose] - connectEvent*: AsyncEvent - acceptEvent*: AsyncEvent + conn: DtlsConn # Underlying DTLS Connection + sctpSocket*: ptr socket # Current usrsctp socket + + + state*: SctpState # Current Sctp State + onClose: seq[SctpConnOnClose] # List of procedure to run while closing a connection + + connectEvent*: AsyncEvent # Event fired when the connection is connected + acceptEvent*: AsyncEvent # Event fired when the connection is accepted + + # Infinite loop reading on the underlying DTLS Connection. readLoop: Future[void].Raising([CancelledError, WebRtcError]) - sctpSocket*: ptr socket - dataRecv: AsyncQueue[SctpMessage] - sendQueue: seq[byte] + + dataRecv: AsyncQueue[SctpMessage] # Queue of messages to be read + sendQueue: seq[byte] # Queue of messages to be sent proc remoteAddress*(self: SctpConn): TransportAddress = if self.conn.isNil(): @@ -106,7 +126,6 @@ proc recvCallback*(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = warn "usrsctp_recvv", error = sctpStrerror() return elif n > 0: - # It might be necessary to check if infotype == SCTP_RECVV_RCVINFO message.data.delete(n ..< message.data.len()) trace "message info from handle upcall", msginfo = message.info message.params = SctpMessageParameters( From fa54c20f02f8f49b11d51732130ef0fedef7d4fe Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 4 Oct 2024 10:34:53 +0200 Subject: [PATCH 65/69] docs: add why ws2_32 and iphlpapi are necessary on windows --- webrtc.nimble | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webrtc.nimble b/webrtc.nimble index 5b82bea..91134e9 100644 --- a/webrtc.nimble +++ b/webrtc.nimble @@ -24,6 +24,8 @@ var cfg = " --threads:on --opt:speed" when defined(windows): + # ws2_32 is required by MbedTLS and usrsctp + # iphlpapi is required by usrsctp cfg = cfg & " --clib:ws2_32 --clib:iphlpapi" import hashes From 48e7d44766d184dd3fc12294db5ccf8d0b6f918c Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 4 Oct 2024 10:38:40 +0200 Subject: [PATCH 66/69] chore: remove empty line --- webrtc/sctp/sctp_connection.nim | 1 - 1 file changed, 1 deletion(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index e70258f..78cd5f0 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -53,7 +53,6 @@ type conn: DtlsConn # Underlying DTLS Connection sctpSocket*: ptr socket # Current usrsctp socket - state*: SctpState # Current Sctp State onClose: seq[SctpConnOnClose] # List of procedure to run while closing a connection From c1fc6dc1875628b8514067a113398a60bc835fde Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 11 Oct 2024 12:48:54 +0200 Subject: [PATCH 67/69] feat: add the option to gracefully close outgoing channels --- webrtc/sctp/sctp_connection.nim | 53 ++++++++++++++++++++++++++++++++- webrtc/sctp/sctp_transport.nim | 2 +- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 78cd5f0..d671cec 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -14,7 +14,9 @@ import ./sctp_utils, ../errors, ../dtls/dtls_connection logScope: topics = "webrtc sctp_connection" -const SctpConnTracker* = "webrtc.sctp.conn" +const + SctpConnTracker* = "webrtc.sctp.conn" + IPPROTO_SCTP = 132 # Official IANA number type SctpConnOnClose* = proc() {.raises: [], gcsafe.} @@ -261,10 +263,59 @@ proc write*( ) {.async: (raises: [CancelledError, WebRtcError]).} = await self.write(s.toBytes()) +type + # This object is a workaround, srs_stream_list in usrsctp is an + # UncheckedArray, and they're not assignable. + sctp_reset_streams_workaround = object + srs_assoc_id: sctp_assoc_t + srs_flags: uint16 + srs_number_streams: uint16 + srs_stream_list: array[1, uint16] + +proc closeChannel*(self: SctpConn, streamId: uint16) = + ## Resets a specific outgoing SCTP stream identified by + ## streamId to close the associated DataChannel. + var srs: sctp_reset_streams_workaround + let len = sizeof(srs) + + srs.srs_flags = SCTP_STREAM_RESET_OUTGOING + srs.srs_number_streams = 1 + srs.srs_stream_list[0] = streamId + let ret = usrsctp_setsockopt( + self.sctpSocket, + IPPROTO_SCTP, + SCTP_RESET_STREAMS, + addr srs, + len.Socklen + ) + if ret < 0: + raise newException(WebRtcError, "SCTP - Close channel failed: " & sctpStrerror()) + +proc closeAllChannels*(self: SctpConn) = + ## Resets all outgoing SCTP streams, effectively closing all + ## open DataChannels for the current SCTP connection. + var srs: sctp_reset_streams_workaround + let len = sizeof(srs) - sizeof(srs.srs_stream_list) + + srs.srs_flags = SCTP_STREAM_RESET_OUTGOING + srs.srs_number_streams = 0 # 0 means all channels + let ret = usrsctp_setsockopt( + self.sctpSocket, + IPPROTO_SCTP, + SCTP_RESET_STREAMS, + addr srs, + len.Socklen + ) + if ret < 0: + raise newException(WebRtcError, "SCTP - Close all channels failed: " & sctpStrerror()) + proc close*(self: SctpConn) {.async: (raises: [CancelledError, WebRtcError]).} = + ## Closes the entire SCTP connection by resetting all channels, + ## deregistering the address, stopping the read loop, and cleaning up resources. if self.state == SctpClosed: debug "Try to close SctpConn twice" return + self.closeAllChannels() usrsctp_deregister_address(cast[pointer](self)) self.usrsctpAwait: self.sctpSocket.usrsctp_close() diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 5339f18..7aef541 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -10,7 +10,7 @@ import tables, bitops, nativesockets, strutils, sequtils import usrsctp, chronos, chronicles import - ./[sctp_connection, sctp_utils], ../errors, ../dtls/[dtls_transport, dtls_connection] + ./[sctp_connection, sctp_utils], ../errors, ../dtls/dtls_transport export chronicles From b63044bd3f8416b16132afc3dc04797f3f6aa0ab Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 11 Oct 2024 13:09:14 +0200 Subject: [PATCH 68/69] chore: add a trace of which notification we receive --- webrtc/sctp/sctp_connection.nim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index d671cec..97aa631 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -134,7 +134,8 @@ proc recvCallback*(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = streamId: message.info.recvv_rcvinfo.rcv_sid, ) if bitand(flags, MSG_NOTIFICATION) != 0: - trace "Notification received", length = n + let notif = cast[ptr sctp_notification](data) + trace "Notification received", notifType = notif.sn_header.sn_type else: try: conn.dataRecv.addLastNoWait(message) From 2e6055a142205c4d8b49e12f155d8dadf4521be2 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Mon, 14 Oct 2024 11:53:50 +0200 Subject: [PATCH 69/69] fix: change Socklen into SockLen --- webrtc/sctp/sctp_connection.nim | 4 ++-- webrtc/sctp/sctp_transport.nim | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/webrtc/sctp/sctp_connection.nim b/webrtc/sctp/sctp_connection.nim index 97aa631..4c4b887 100644 --- a/webrtc/sctp/sctp_connection.nim +++ b/webrtc/sctp/sctp_connection.nim @@ -287,7 +287,7 @@ proc closeChannel*(self: SctpConn, streamId: uint16) = IPPROTO_SCTP, SCTP_RESET_STREAMS, addr srs, - len.Socklen + len.SockLen ) if ret < 0: raise newException(WebRtcError, "SCTP - Close channel failed: " & sctpStrerror()) @@ -305,7 +305,7 @@ proc closeAllChannels*(self: SctpConn) = IPPROTO_SCTP, SCTP_RESET_STREAMS, addr srs, - len.Socklen + len.SockLen ) if ret < 0: raise newException(WebRtcError, "SCTP - Close all channels failed: " & sctpStrerror()) diff --git a/webrtc/sctp/sctp_transport.nim b/webrtc/sctp/sctp_transport.nim index 7aef541..45ec122 100644 --- a/webrtc/sctp/sctp_transport.nim +++ b/webrtc/sctp/sctp_transport.nim @@ -42,7 +42,7 @@ proc handleAccept(sock: ptr socket, data: pointer, flags: cint) {.cdecl.} = # Callback procedure called when a connection is about to be accepted. var sconn: Sockaddr_conn - slen: Socklen = sizeof(Sockaddr_conn).uint32 + slen: SockLen = sizeof(Sockaddr_conn).uint32 let sctp = cast[Sctp](data) sctpSocket =