logos-messaging-nim/waku/net/auto_port.nim
Fabiana Cecin 71a369ffad
feat: allow a port value of zero for service ports (auto-assign port) (#3828)
* any port set to 0 on conf results in a random port bound
* Debug API MyBoundPorts reports actually bound ports for all services, reports 0 if disabled
* write back bound values to both WakuConf and WakuNode.ports
* setupDiscoveryV5 returns Result and errors out on port 0
* rename setupAndStartDiscv5WithAutoPort to setupAndStartDiscv5
* updateWaku ENR rebuild now runs after discv5 startup
* Add DefaultP2pTcpPort, DefaultDiscv5UdpPort, DefaultWebSocketPort, DefaultRestPort, DefaultMetricsHttpPort
* add tests
2026-05-11 15:22:22 -03:00

49 lines
1.4 KiB
Nim

{.push raises: [].}
import std/[net, random]
import chronos, results
const
AutoPortRetryCount* = 20
AutoPortMin = 50000'u16
AutoPortMax = 59000'u16
AutoPortAttemptTimeout = chronos.seconds(30)
proc getAutoPort*(): uint16 =
var rng = initRand()
uint16(rng.rand(AutoPortMin.int .. AutoPortMax.int))
proc tryWithAutoPort*[T](
startingPort: Port,
attempt: proc(p: Port): Future[Result[T, string]] {.async: (raises: []).},
): Future[Result[T, string]] {.async: (raises: []).} =
## If `startingPort == Port(0)`, call `attempt` up to `AutoPortRetryCount`
## times with random ports. Otherwise call it once with `startingPort`.
## Returns the first ok or the last err.
let autoMode = startingPort == Port(0)
let attempts = if autoMode: AutoPortRetryCount else: 1
var lastErr = ""
for i in 1 .. attempts:
let port =
if autoMode:
Port(getAutoPort())
else:
startingPort
let fut = attempt(port)
let res =
try:
if await fut.withTimeout(AutoPortAttemptTimeout):
await fut
else:
fut.cancelSoon()
Result[T, string].err("bind attempt timed out")
except CancelledError:
fut.cancelSoon()
Result[T, string].err("bind attempt cancelled")
if res.isOk():
return ok(res.get())
lastErr = res.error
if autoMode:
return err("auto-port exhausted; last error: " & lastErr)
return err("port bind failed: " & lastErr)