2024-05-24 12:14:30 +00:00
|
|
|
# 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
|
|
|
|
import chronos, chronicles, bearssl
|
2024-06-12 10:19:27 +00:00
|
|
|
import stun_connection, stun_message, ../udp_transport
|
2024-05-24 12:14:30 +00:00
|
|
|
|
|
|
|
logScope:
|
|
|
|
topics = "webrtc stun stun_transport"
|
|
|
|
|
|
|
|
const
|
|
|
|
StunMaxPendingConnections = 512
|
|
|
|
|
|
|
|
type
|
|
|
|
Stun* = ref object
|
|
|
|
connections: Table[TransportAddress, StunConn]
|
|
|
|
pendingConn: AsyncQueue[StunConn]
|
|
|
|
readingLoop: Future[void]
|
2024-06-12 10:19:27 +00:00
|
|
|
udp: UdpTransport
|
2024-05-24 12:14:30 +00:00
|
|
|
|
|
|
|
usernameProvider: StunUsernameProvider
|
|
|
|
usernameChecker: StunUsernameChecker
|
|
|
|
passwordProvider: StunPasswordProvider
|
|
|
|
|
|
|
|
rng: ref HmacDrbgContext
|
|
|
|
|
|
|
|
proc accept*(self: Stun): Future[StunConn] {.async: (raises: [CancelledError]).} =
|
|
|
|
## Accept a Stun Connection
|
|
|
|
##
|
|
|
|
var res: StunConn
|
|
|
|
while true:
|
|
|
|
res = await self.pendingConn.popFirst()
|
|
|
|
if res.closed != true: # The connection could be closed before being accepted
|
|
|
|
break
|
|
|
|
return res
|
|
|
|
|
|
|
|
proc connect*(
|
|
|
|
self: Stun,
|
|
|
|
raddr: TransportAddress
|
|
|
|
): Future[StunConn] {.async: (raises: []).} =
|
|
|
|
## Connect to a remote address, creating a Stun Connection
|
|
|
|
##
|
|
|
|
self.connections.withValue(raddr, res):
|
|
|
|
return res[]
|
|
|
|
do:
|
2024-06-12 10:19:27 +00:00
|
|
|
let res = StunConn.new(self.udp, raddr, false, self.usernameProvider,
|
2024-05-24 12:14:30 +00:00
|
|
|
self.usernameChecker, self.passwordProvider, self.rng)
|
|
|
|
self.connections[raddr] = res
|
|
|
|
return res
|
|
|
|
|
|
|
|
proc cleanupStunConn(self: Stun, conn: StunConn) {.async: (raises: []).} =
|
|
|
|
# Waiting for a connection to be closed to remove it from the table
|
|
|
|
try:
|
|
|
|
await conn.join()
|
|
|
|
self.connections.del(conn.raddr)
|
|
|
|
except CancelledError as exc:
|
|
|
|
warn "Error cleaning up Stun Connection", error=exc.msg
|
|
|
|
|
|
|
|
proc stunReadLoop(self: Stun) {.async: (raises: [CancelledError]).} =
|
|
|
|
while true:
|
2024-06-12 10:19:27 +00:00
|
|
|
let (buf, raddr) = await self.udp.read()
|
2024-05-24 12:14:30 +00:00
|
|
|
var stunConn: StunConn
|
|
|
|
if not self.connections.hasKey(raddr):
|
2024-06-12 10:19:27 +00:00
|
|
|
stunConn = StunConn.new(self.udp, raddr, true, self.usernameProvider,
|
2024-05-24 12:14:30 +00:00
|
|
|
self.usernameChecker, self.passwordProvider, self.rng)
|
|
|
|
self.connections[raddr] = stunConn
|
|
|
|
await self.pendingConn.addLast(stunConn)
|
|
|
|
asyncSpawn self.cleanupStunConn(stunConn)
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
stunConn = self.connections[raddr]
|
|
|
|
except KeyError as exc:
|
|
|
|
doAssert(false, "Should never happen")
|
|
|
|
|
|
|
|
if isStunMessage(buf):
|
|
|
|
await stunConn.stunMsgs.addLast(buf)
|
|
|
|
else:
|
|
|
|
await stunConn.dataRecv.addLast(buf)
|
|
|
|
|
|
|
|
proc stop(self: Stun) =
|
|
|
|
## Stop the Stun transport and close all the connections
|
|
|
|
##
|
|
|
|
for conn in self.connections.values():
|
|
|
|
conn.close()
|
|
|
|
self.readingLoop.cancelSoon()
|
|
|
|
|
|
|
|
proc defaultUsernameProvider(): string = ""
|
|
|
|
proc defaultUsernameChecker(username: seq[byte]): bool = true
|
|
|
|
proc defaultPasswordProvider(username: seq[byte]): seq[byte] = @[]
|
|
|
|
|
|
|
|
proc new*(
|
|
|
|
T: type Stun,
|
2024-06-12 10:19:27 +00:00
|
|
|
udp: UdpTransport,
|
2024-05-24 12:14:30 +00:00
|
|
|
usernameProvider: StunUsernameProvider = defaultUsernameProvider,
|
|
|
|
usernameChecker: StunUsernameChecker = defaultUsernameChecker,
|
|
|
|
passwordProvider: StunPasswordProvider = defaultPasswordProvider,
|
|
|
|
rng: ref HmacDrbgContext = HmacDrbgContext.new(),
|
|
|
|
): T =
|
|
|
|
## Initialize the Stun transport
|
|
|
|
##
|
|
|
|
var self = T(
|
2024-06-12 10:19:27 +00:00
|
|
|
udp: udp,
|
2024-05-24 12:14:30 +00:00
|
|
|
usernameProvider: usernameProvider,
|
|
|
|
usernameChecker: usernameChecker,
|
|
|
|
passwordProvider: passwordProvider,
|
|
|
|
rng: rng
|
|
|
|
)
|
|
|
|
self.readingLoop = stunReadLoop()
|
|
|
|
self.pendingConn = newAsyncQueue[StunConn](StunMaxPendingConnections)
|
|
|
|
return self
|