Add automatic constructors for TCP and UDP transports. (#512)

* Add automatic constructors for TCP and UDP transports.

* Add port number argument.
Add some documentation comments.
Fix tests.

* Make datagram test use request/response scheme.

* Add helper.

* Fix issue with non-zero port setups.
Add test.

* Fix tests to probe ports.

* Attempt to fix MacOS issue.

* Add Opt[IpAddress].
Make IPv4 mapping to IPv6 space automatic.

* Add tests.

* Add stream capabilities.

* Fix Linux issues.

* Make getTransportFlags() available for all OSes.

* Fix one more compilation issue.

* Workaround weird compiler bug.

* Fix forgotten typed version of constructor.

* Make single source for addresses calculation.

* Add one more check into tests.

* Fix flags not being set in transport constructor.

* Fix post-rebase issues with flags not being set.

* Address review comments.
This commit is contained in:
Eugene Kabanov 2024-04-13 03:04:42 +03:00 committed by GitHub
parent 8e49df1400
commit 0d050d5823
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 1257 additions and 257 deletions

View File

@ -10,6 +10,7 @@
{.push raises: [].}
import std/[strutils]
import results
import stew/[base10, byteutils]
import ".."/[config, asyncloop, osdefs, oserrno, handles]
@ -18,7 +19,7 @@ from std/net import Domain, `==`, IpAddress, IpAddressFamily, parseIpAddress,
from std/nativesockets import toInt, `$`
export Domain, `==`, IpAddress, IpAddressFamily, parseIpAddress, SockType,
Protocol, Port, toInt, `$`
Protocol, Port, toInt, `$`, results
const
DefaultStreamBufferSize* = chronosTransportDefaultBufferSize
@ -29,7 +30,7 @@ type
ServerFlags* = enum
## Server's flags
ReuseAddr, ReusePort, TcpNoDelay, NoAutoRead, GCUserData, FirstPipe,
NoPipeFlash, Broadcast
NoPipeFlash, Broadcast, V4Mapped
DualStackType* {.pure.} = enum
Auto, Enabled, Disabled, Default
@ -200,6 +201,15 @@ proc `$`*(address: TransportAddress): string =
of AddressFamily.None:
"None"
proc toIpAddress*(address: TransportAddress): IpAddress =
case address.family
of AddressFamily.IPv4:
IpAddress(family: IpAddressFamily.IPv4, address_v4: address.address_v4)
of AddressFamily.IPv6:
IpAddress(family: IpAddressFamily.IPv6, address_v6: address.address_v6)
else:
raiseAssert "IpAddress do not support address family " & $address.family
proc toHex*(address: TransportAddress): string =
## Returns hexadecimal representation of ``address``.
case address.family
@ -783,3 +793,25 @@ proc setDualstack*(socket: AsyncFD,
else:
? getDomain(socket)
setDualstack(socket, family, flag)
proc getAutoAddress*(port: Port): TransportAddress =
var res =
if isAvailable(AddressFamily.IPv6):
AnyAddress6
else:
AnyAddress
res.port = port
res
proc getAutoAddresses*(
localPort: Port,
remotePort: Port
): tuple[local: TransportAddress, remote: TransportAddress] =
var (local, remote) =
if isAvailable(AddressFamily.IPv6):
(AnyAddress6, AnyAddress6)
else:
(AnyAddress, AnyAddress)
local.port = localPort
remote.port = remotePort
(local, remote)

View File

@ -10,11 +10,14 @@
{.push raises: [].}
import std/deques
import results
when not(defined(windows)): import ".."/selectors2
import ".."/[asyncloop, osdefs, oserrno, osutils, handles]
import "."/common
import "."/[common, ipnet]
import stew/ptrops
export results
type
VectorKind = enum
WithoutAddress, WithAddress
@ -60,29 +63,78 @@ type
const
DgramTransportTrackerName* = "datagram.transport"
proc getRemoteAddress(transp: DatagramTransport,
address: Sockaddr_storage, length: SockLen,
): TransportAddress =
var raddr: TransportAddress
fromSAddr(unsafeAddr address, length, raddr)
if ServerFlags.V4Mapped in transp.flags:
if raddr.isV4Mapped(): raddr.toIPv4() else: raddr
else:
raddr
proc getRemoteAddress(transp: DatagramTransport): TransportAddress =
transp.getRemoteAddress(transp.raddr, transp.ralen)
proc setRemoteAddress(transp: DatagramTransport,
address: TransportAddress): TransportAddress =
let
fixedAddress =
when defined(windows):
windowsAnyAddressFix(address)
else:
address
remoteAddress =
if ServerFlags.V4Mapped in transp.flags:
if address.family == AddressFamily.IPv4:
fixedAddress.toIPv6()
else:
fixedAddress
else:
fixedAddress
toSAddr(remoteAddress, transp.waddr, transp.walen)
remoteAddress
proc remoteAddress2*(
transp: DatagramTransport
): Result[TransportAddress, OSErrorCode] =
## Returns ``transp`` remote socket address.
if transp.remote.family == AddressFamily.None:
var
saddr: Sockaddr_storage
slen = SockLen(sizeof(saddr))
if getpeername(SocketHandle(transp.fd), cast[ptr SockAddr](addr saddr),
addr slen) != 0:
return err(osLastError())
transp.remote = transp.getRemoteAddress(saddr, slen)
ok(transp.remote)
proc localAddress2*(
transp: DatagramTransport
): Result[TransportAddress, OSErrorCode] =
## Returns ``transp`` local socket address.
if transp.local.family == AddressFamily.None:
var
saddr: Sockaddr_storage
slen = SockLen(sizeof(saddr))
if getsockname(SocketHandle(transp.fd), cast[ptr SockAddr](addr saddr),
addr slen) != 0:
return err(osLastError())
fromSAddr(addr saddr, slen, transp.local)
ok(transp.local)
func toException(v: OSErrorCode): ref TransportOsError =
getTransportOsError(v)
proc remoteAddress*(transp: DatagramTransport): TransportAddress {.
raises: [TransportOsError].} =
## Returns ``transp`` remote socket address.
if transp.remote.family == AddressFamily.None:
var saddr: Sockaddr_storage
var slen = SockLen(sizeof(saddr))
if getpeername(SocketHandle(transp.fd), cast[ptr SockAddr](addr saddr),
addr slen) != 0:
raiseTransportOsError(osLastError())
fromSAddr(addr saddr, slen, transp.remote)
transp.remote
remoteAddress2(transp).tryGet()
proc localAddress*(transp: DatagramTransport): TransportAddress {.
raises: [TransportOsError].} =
## Returns ``transp`` local socket address.
if transp.local.family == AddressFamily.None:
var saddr: Sockaddr_storage
var slen = SockLen(sizeof(saddr))
if getsockname(SocketHandle(transp.fd), cast[ptr SockAddr](addr saddr),
addr slen) != 0:
raiseTransportOsError(osLastError())
fromSAddr(addr saddr, slen, transp.local)
transp.local
## Returns ``transp`` remote socket address.
localAddress2(transp).tryGet()
template setReadError(t, e: untyped) =
(t).state.incl(ReadError)
@ -124,8 +176,8 @@ when defined(windows):
transp.setWriterWSABuffer(vector)
let ret =
if vector.kind == WithAddress:
var fixedAddress = windowsAnyAddressFix(vector.address)
toSAddr(fixedAddress, transp.waddr, transp.walen)
# We only need `Sockaddr_storage` data here, so result discarded.
discard transp.setRemoteAddress(vector.address)
wsaSendTo(fd, addr transp.wwsabuf, DWORD(1), addr bytesCount,
DWORD(0), cast[ptr SockAddr](addr transp.waddr),
cint(transp.walen),
@ -159,22 +211,24 @@ when defined(windows):
proc readDatagramLoop(udata: pointer) =
var
bytesCount: uint32
raddr: TransportAddress
var ovl = cast[PtrCustomOverlapped](udata)
var transp = cast[DatagramTransport](ovl.data.udata)
ovl = cast[PtrCustomOverlapped](udata)
let transp = cast[DatagramTransport](ovl.data.udata)
while true:
if ReadPending in transp.state:
## Continuation
transp.state.excl(ReadPending)
let err = transp.rovl.data.errCode
let
err = transp.rovl.data.errCode
remoteAddress = transp.getRemoteAddress()
case err
of OSErrorCode(-1):
let bytesCount = transp.rovl.data.bytesCount
if bytesCount == 0:
transp.state.incl({ReadEof, ReadPaused})
fromSAddr(addr transp.raddr, transp.ralen, raddr)
transp.buflen = int(bytesCount)
asyncSpawn transp.function(transp, raddr)
asyncSpawn transp.function(transp, remoteAddress)
of ERROR_OPERATION_ABORTED:
# CancelIO() interrupt or closeSocket() call.
transp.state.incl(ReadPaused)
@ -189,7 +243,7 @@ when defined(windows):
transp.setReadError(err)
transp.state.incl(ReadPaused)
transp.buflen = 0
asyncSpawn transp.function(transp, raddr)
asyncSpawn transp.function(transp, remoteAddress)
else:
## Initiation
if transp.state * {ReadEof, ReadClosed, ReadError} == {}:
@ -220,7 +274,7 @@ when defined(windows):
transp.state.incl(ReadPaused)
transp.setReadError(err)
transp.buflen = 0
asyncSpawn transp.function(transp, raddr)
asyncSpawn transp.function(transp, transp.getRemoteAddress())
else:
# Transport closure happens in callback, and we not started new
# WSARecvFrom session.
@ -341,18 +395,25 @@ when defined(windows):
closeSocket(localSock)
raiseTransportOsError(err)
res.flags =
block:
# Add `V4Mapped` flag when `::` address is used and dualstack is
# set to enabled or auto.
var res = flags
if (local.family == AddressFamily.IPv6) and local.isAnyLocal():
if dualstack in {DualStackType.Enabled, DualStackType.Auto}:
res.incl(ServerFlags.V4Mapped)
res
if remote.port != Port(0):
var fixedAddress = windowsAnyAddressFix(remote)
var saddr: Sockaddr_storage
var slen: SockLen
toSAddr(fixedAddress, saddr, slen)
if connect(SocketHandle(localSock), cast[ptr SockAddr](addr saddr),
slen) != 0:
let remoteAddress = res.setRemoteAddress(remote)
if connect(SocketHandle(localSock), cast[ptr SockAddr](addr res.waddr),
res.walen) != 0:
let err = osLastError()
if sock == asyncInvalidSocket:
closeSocket(localSock)
raiseTransportOsError(err)
res.remote = fixedAddress
res.remote = remoteAddress
res.fd = localSock
res.function = cbproc
@ -362,12 +423,12 @@ when defined(windows):
res.state = {ReadPaused, WritePaused}
res.future = Future[void].Raising([]).init(
"datagram.transport", {FutureFlag.OwnCancelSchedule})
res.rovl.data = CompletionData(cb: readDatagramLoop,
udata: cast[pointer](res))
res.wovl.data = CompletionData(cb: writeDatagramLoop,
udata: cast[pointer](res))
res.rwsabuf = WSABUF(buf: cast[cstring](baseAddr res.buffer),
len: ULONG(len(res.buffer)))
res.rovl.data = CompletionData(
cb: readDatagramLoop, udata: cast[pointer](res))
res.wovl.data = CompletionData(
cb: writeDatagramLoop, udata: cast[pointer](res))
res.rwsabuf = WSABUF(
buf: cast[cstring](baseAddr res.buffer), len: ULONG(len(res.buffer)))
GC_ref(res)
# Start tracking transport
trackCounter(DgramTransportTrackerName)
@ -380,10 +441,10 @@ else:
# Linux/BSD/MacOS part
proc readDatagramLoop(udata: pointer) {.raises: [].}=
var raddr: TransportAddress
doAssert(not isNil(udata))
let transp = cast[DatagramTransport](udata)
let fd = SocketHandle(transp.fd)
let
transp = cast[DatagramTransport](udata)
fd = SocketHandle(transp.fd)
if int(fd) == 0:
## This situation can be happen, when there events present
## after transport was closed.
@ -398,9 +459,8 @@ else:
cast[ptr SockAddr](addr transp.raddr),
addr transp.ralen)
if res >= 0:
fromSAddr(addr transp.raddr, transp.ralen, raddr)
transp.buflen = res
asyncSpawn transp.function(transp, raddr)
asyncSpawn transp.function(transp, transp.getRemoteAddress())
else:
let err = osLastError()
case err
@ -409,14 +469,15 @@ else:
else:
transp.buflen = 0
transp.setReadError(err)
asyncSpawn transp.function(transp, raddr)
asyncSpawn transp.function(transp, transp.getRemoteAddress())
break
proc writeDatagramLoop(udata: pointer) =
var res: int
doAssert(not isNil(udata))
var transp = cast[DatagramTransport](udata)
let fd = SocketHandle(transp.fd)
let
transp = cast[DatagramTransport](udata)
fd = SocketHandle(transp.fd)
if int(fd) == 0:
## This situation can be happen, when there events present
## after transport was closed.
@ -428,7 +489,8 @@ else:
let vector = transp.queue.popFirst()
while true:
if vector.kind == WithAddress:
toSAddr(vector.address, transp.waddr, transp.walen)
# We only need `Sockaddr_storage` data here, so result discarded.
discard transp.setRemoteAddress(vector.address)
res = osdefs.sendto(fd, vector.buf, vector.buflen, MSG_NOSIGNAL,
cast[ptr SockAddr](addr transp.waddr),
transp.walen)
@ -551,21 +613,28 @@ else:
closeSocket(localSock)
raiseTransportOsError(err)
res.flags =
block:
# Add `V4Mapped` flag when `::` address is used and dualstack is
# set to enabled or auto.
var res = flags
if (local.family == AddressFamily.IPv6) and local.isAnyLocal():
if dualstack != DualStackType.Disabled:
res.incl(ServerFlags.V4Mapped)
res
if remote.port != Port(0):
var saddr: Sockaddr_storage
var slen: SockLen
toSAddr(remote, saddr, slen)
if connect(SocketHandle(localSock), cast[ptr SockAddr](addr saddr),
slen) != 0:
let remoteAddress = res.setRemoteAddress(remote)
if connect(SocketHandle(localSock), cast[ptr SockAddr](addr res.waddr),
res.walen) != 0:
let err = osLastError()
if sock == asyncInvalidSocket:
closeSocket(localSock)
raiseTransportOsError(err)
res.remote = remote
res.remote = remoteAddress
res.fd = localSock
res.function = cbproc
res.flags = flags
res.buffer = newSeq[byte](bufferSize)
res.queue = initDeque[GramVector]()
res.udata = udata
@ -605,6 +674,24 @@ proc close*(transp: DatagramTransport) =
transp.state.incl({WriteClosed, ReadClosed})
closeSocket(transp.fd, continuation)
proc getTransportAddresses(
local, remote: Opt[IpAddress],
localPort, remotePort: Port
): tuple[local: TransportAddress, remote: TransportAddress] =
let
(localAuto, remoteAuto) = getAutoAddresses(localPort, remotePort)
lres =
if local.isSome():
initTAddress(local.get(), localPort)
else:
localAuto
rres =
if remote.isSome():
initTAddress(remote.get(), remotePort)
else:
remoteAuto
(lres, rres)
proc newDatagramTransportCommon(cbproc: UnsafeDatagramCallback,
remote: TransportAddress,
local: TransportAddress,
@ -824,6 +911,92 @@ proc newDatagramTransport6*[T](cbproc: UnsafeDatagramCallback,
cast[pointer](udata), child, bufSize, ttl,
dualstack)
proc newDatagramTransport*(cbproc: DatagramCallback,
localPort: Port,
remotePort: Port,
local: Opt[IpAddress] = Opt.none(IpAddress),
remote: Opt[IpAddress] = Opt.none(IpAddress),
flags: set[ServerFlags] = {},
udata: pointer = nil,
child: DatagramTransport = nil,
bufSize: int = DefaultDatagramBufferSize,
ttl: int = 0,
dualstack = DualStackType.Auto
): DatagramTransport {.
raises: [TransportOsError].} =
## Create new UDP datagram transport (IPv6) and bind it to ANY_ADDRESS.
## Depending on OS settings procedure perform an attempt to create transport
## using IPv6 ANY_ADDRESS, if its not available it will try to bind transport
## to IPv4 ANY_ADDRESS.
##
## ``cbproc`` - callback which will be called, when new datagram received.
## ``localPort`` - local peer's port number.
## ``remotePort`` - remote peer's port number.
## ``local`` - optional local peer's IPv4/IPv6 address.
## ``remote`` - optional remote peer's IPv4/IPv6 address.
## ``sock`` - application-driven socket to use.
## ``flags`` - flags that will be applied to socket.
## ``udata`` - custom argument which will be passed to ``cbproc``.
## ``bufSize`` - size of internal buffer.
## ``ttl`` - TTL for UDP datagram packet (only usable when flags has
## ``Broadcast`` option).
let
(localHost, remoteHost) =
getTransportAddresses(local, remote, localPort, remotePort)
newDatagramTransportCommon(cbproc, remoteHost, localHost, asyncInvalidSocket,
flags, cast[pointer](udata), child, bufSize,
ttl, dualstack)
proc newDatagramTransport*(cbproc: DatagramCallback,
localPort: Port,
local: Opt[IpAddress] = Opt.none(IpAddress),
flags: set[ServerFlags] = {},
udata: pointer = nil,
child: DatagramTransport = nil,
bufSize: int = DefaultDatagramBufferSize,
ttl: int = 0,
dualstack = DualStackType.Auto
): DatagramTransport {.
raises: [TransportOsError].} =
newDatagramTransport(cbproc, localPort, Port(0), local, Opt.none(IpAddress),
flags, udata, child, bufSize, ttl, dualstack)
proc newDatagramTransport*[T](cbproc: DatagramCallback,
localPort: Port,
remotePort: Port,
local: Opt[IpAddress] = Opt.none(IpAddress),
remote: Opt[IpAddress] = Opt.none(IpAddress),
flags: set[ServerFlags] = {},
udata: ref T,
child: DatagramTransport = nil,
bufSize: int = DefaultDatagramBufferSize,
ttl: int = 0,
dualstack = DualStackType.Auto
): DatagramTransport {.
raises: [TransportOsError].} =
let
(localHost, remoteHost) =
getTransportAddresses(local, remote, localPort, remotePort)
fflags = flags + {GCUserData}
GC_ref(udata)
newDatagramTransportCommon(cbproc, remoteHost, localHost, asyncInvalidSocket,
fflags, cast[pointer](udata), child, bufSize, ttl,
dualstack)
proc newDatagramTransport*[T](cbproc: DatagramCallback,
localPort: Port,
local: Opt[IpAddress] = Opt.none(IpAddress),
flags: set[ServerFlags] = {},
udata: ref T,
child: DatagramTransport = nil,
bufSize: int = DefaultDatagramBufferSize,
ttl: int = 0,
dualstack = DualStackType.Auto
): DatagramTransport {.
raises: [TransportOsError].} =
newDatagramTransport(cbproc, localPort, Port(0), local, Opt.none(IpAddress),
flags, udata, child, bufSize, ttl, dualstack)
proc join*(transp: DatagramTransport): Future[void] {.
async: (raw: true, raises: [CancelledError]).} =
## Wait until the transport ``transp`` will be closed.

View File

@ -11,8 +11,11 @@
import std/deques
import stew/ptrops
import results
import ".."/[asyncloop, config, handles, bipbuffer, osdefs, osutils, oserrno]
import ./common
import ./[common, ipnet]
export results
type
VectorKind = enum
@ -48,7 +51,8 @@ type
# get stuck on transport `close()`.
# Please use this flag only if you are making both client and server in
# the same thread.
TcpNoDelay # deprecated: Use SocketFlags.TcpNoDelay
TcpNoDelay, # deprecated: Use SocketFlags.TcpNoDelay
V4Mapped
SocketFlags* {.pure.} = enum
TcpNoDelay,
@ -101,6 +105,7 @@ else:
error: ref TransportError # Current error
queue: Deque[StreamVector] # Writer queue
future: Future[void].Raising([]) # Stream life future
flags: set[TransportFlags] # Internal flags
case kind*: TransportKind
of TransportKind.Socket:
domain: Domain # Socket transport domain (IPv4/IPv6)
@ -138,31 +143,59 @@ type
init*: TransportInitCallback # callback which will be called before
# transport for new client
proc getRemoteAddress(transp: StreamTransport,
address: Sockaddr_storage, length: SockLen,
): TransportAddress =
var raddr: TransportAddress
fromSAddr(unsafeAddr address, length, raddr)
if TransportFlags.V4Mapped in transp.flags:
if raddr.isV4Mapped(): raddr.toIPv4() else: raddr
else:
raddr
proc remoteAddress2*(
transp: StreamTransport
): Result[TransportAddress, OSErrorCode] =
## Returns ``transp`` remote socket address.
if transp.remote.family == AddressFamily.None:
var
saddr: Sockaddr_storage
slen = SockLen(sizeof(saddr))
if getpeername(SocketHandle(transp.fd), cast[ptr SockAddr](addr saddr),
addr slen) != 0:
return err(osLastError())
transp.remote = transp.getRemoteAddress(saddr, slen)
ok(transp.remote)
proc localAddress2*(
transp: StreamTransport
): Result[TransportAddress, OSErrorCode] =
## Returns ``transp`` local socket address.
if transp.local.family == AddressFamily.None:
var
saddr: Sockaddr_storage
slen = SockLen(sizeof(saddr))
if getsockname(SocketHandle(transp.fd), cast[ptr SockAddr](addr saddr),
addr slen) != 0:
return err(osLastError())
fromSAddr(addr saddr, slen, transp.local)
ok(transp.local)
# TODO(cheatfate): This function should not be public, but for some weird
# reason if we will make it non-public it start generate
# Hint: 'toException' is declared but not used [XDeclaredButNotUsed]
func toException*(v: OSErrorCode): ref TransportOsError =
getTransportOsError(v)
proc remoteAddress*(transp: StreamTransport): TransportAddress {.
raises: [TransportOsError].} =
## Returns ``transp`` remote socket address.
doAssert(transp.kind == TransportKind.Socket, "Socket transport required!")
if transp.remote.family == AddressFamily.None:
var saddr: Sockaddr_storage
var slen = SockLen(sizeof(saddr))
if getpeername(SocketHandle(transp.fd), cast[ptr SockAddr](addr saddr),
addr slen) != 0:
raiseTransportOsError(osLastError())
fromSAddr(addr saddr, slen, transp.remote)
transp.remote
remoteAddress2(transp).tryGet()
proc localAddress*(transp: StreamTransport): TransportAddress {.
raises: [TransportOsError].} =
## Returns ``transp`` local socket address.
doAssert(transp.kind == TransportKind.Socket, "Socket transport required!")
if transp.local.family == AddressFamily.None:
var saddr: Sockaddr_storage
var slen = SockLen(sizeof(saddr))
if getsockname(SocketHandle(transp.fd), cast[ptr SockAddr](addr saddr),
addr slen) != 0:
raiseTransportOsError(osLastError())
fromSAddr(addr saddr, slen, transp.local)
transp.local
## Returns ``transp`` remote socket address.
localAddress2(transp).tryGet()
proc localAddress*(server: StreamServer): TransportAddress =
## Returns ``server`` bound local socket address.
@ -220,6 +253,12 @@ proc clean(transp: StreamTransport) {.inline.} =
template toUnchecked*(a: untyped): untyped =
cast[ptr UncheckedArray[byte]](a)
func getTransportFlags(server: StreamServer): set[TransportFlags] =
if ServerFlags.V4Mapped in server.flags:
{TransportFlags.V4Mapped}
else:
{}
when defined(windows):
template zeroOvelappedOffset(t: untyped) =
@ -574,13 +613,15 @@ when defined(windows):
break
proc newStreamSocketTransport(sock: AsyncFD, bufsize: int,
child: StreamTransport): StreamTransport =
child: StreamTransport,
flags: set[TransportFlags]): StreamTransport =
var transp: StreamTransport
if not(isNil(child)):
transp = child
else:
transp = StreamTransport(kind: TransportKind.Socket)
transp.fd = sock
transp.flags = flags
transp.rovl.data = CompletionData(cb: readStreamLoop,
udata: cast[pointer](transp))
transp.wovl.data = CompletionData(cb: writeStreamLoop,
@ -617,25 +658,27 @@ when defined(windows):
GC_ref(transp)
transp
proc bindToDomain(handle: AsyncFD, domain: Domain): bool =
if domain == Domain.AF_INET6:
proc bindToDomain(handle: AsyncFD,
family: AddressFamily): Result[void, OSErrorCode] =
case family
of AddressFamily.IPv6:
var saddr: Sockaddr_in6
saddr.sin6_family = type(saddr.sin6_family)(osdefs.AF_INET6)
if osdefs.bindSocket(SocketHandle(handle),
cast[ptr SockAddr](addr(saddr)),
sizeof(saddr).SockLen) != 0'i32:
return false
true
elif domain == Domain.AF_INET:
return err(osLastError())
ok()
of AddressFamily.IPv4:
var saddr: Sockaddr_in
saddr.sin_family = type(saddr.sin_family)(osdefs.AF_INET)
if osdefs.bindSocket(SocketHandle(handle),
cast[ptr SockAddr](addr(saddr)),
sizeof(saddr).SockLen) != 0'i32:
return false
true
return err(osLastError())
ok()
else:
raiseAssert "Unsupported domain"
raiseAssert "Unsupported family"
proc connect*(address: TransportAddress,
bufferSize = DefaultStreamBufferSize,
@ -691,26 +734,36 @@ when defined(windows):
retFuture.fail(getTransportOsError(error))
return retFuture
if localAddress != TransportAddress():
if localAddress.family != address.family:
sock.closeSocket()
retFuture.fail(newException(TransportOsError,
"connect local address domain is not equal to target address domain"))
return retFuture
let transportFlags =
block:
# Add `V4Mapped` flag when `::` address is used and dualstack is
# set to enabled or auto.
var res: set[TransportFlags]
if (localAddress.family == AddressFamily.IPv6) and
localAddress.isAnyLocal():
if dualstack in {DualStackType.Enabled, DualStackType.Auto}:
res.incl(TransportFlags.V4Mapped)
res
case localAddress.family
of AddressFamily.IPv4, AddressFamily.IPv6:
var
localAddr: Sockaddr_storage
localAddrLen: SockLen
localAddress.toSAddr(localAddr, localAddrLen)
saddr: Sockaddr_storage
slen: SockLen
toSAddr(localAddress, saddr, slen)
if bindSocket(SocketHandle(sock),
cast[ptr SockAddr](addr localAddr), localAddrLen) != 0:
cast[ptr SockAddr](addr saddr), slen) != 0:
sock.closeSocket()
retFuture.fail(getTransportOsError(osLastError()))
return retFuture
elif not(bindToDomain(sock, raddress.getDomain())):
let err = wsaGetLastError()
sock.closeSocket()
retFuture.fail(getTransportOsError(err))
return retFuture
of AddressFamily.Unix:
raiseAssert "Unsupported local address family"
of AddressFamily.None:
let res = bindToDomain(sock, raddress.family)
if res.isErr():
sock.closeSocket()
retFuture.fail(getTransportOsError(res.error))
return retFuture
proc socketContinuation(udata: pointer) {.gcsafe.} =
var ovl = cast[RefCustomOverlapped](udata)
@ -723,7 +776,8 @@ when defined(windows):
sock.closeSocket()
retFuture.fail(getTransportOsError(err))
else:
let transp = newStreamSocketTransport(sock, bufferSize, child)
let transp = newStreamSocketTransport(sock, bufferSize, child,
transportFlags)
# Start tracking transport
trackCounter(StreamTransportTrackerName)
retFuture.complete(transp)
@ -949,10 +1003,12 @@ when defined(windows):
let transp = server.init(server, server.asock)
ntransp = newStreamSocketTransport(server.asock,
server.bufferSize,
transp)
transp,
server.getTransportFlags())
else:
ntransp = newStreamSocketTransport(server.asock,
server.bufferSize, nil)
server.bufferSize, nil,
server.getTransportFlags())
# Start tracking transport
trackCounter(StreamTransportTrackerName)
asyncSpawn server.function(server, ntransp)
@ -1090,10 +1146,12 @@ when defined(windows):
let transp = server.init(server, server.asock)
ntransp = newStreamSocketTransport(server.asock,
server.bufferSize,
transp)
transp,
server.getTransportFlags())
else:
ntransp = newStreamSocketTransport(server.asock,
server.bufferSize, nil)
server.bufferSize, nil,
server.getTransportFlags())
# Start tracking transport
trackCounter(StreamTransportTrackerName)
retFuture.complete(ntransp)
@ -1446,7 +1504,8 @@ else:
break
proc newStreamSocketTransport(sock: AsyncFD, bufsize: int,
child: StreamTransport): StreamTransport =
child: StreamTransport,
flags: set[TransportFlags]): StreamTransport =
var transp: StreamTransport
if not(isNil(child)):
transp = child
@ -1454,6 +1513,7 @@ else:
transp = StreamTransport(kind: TransportKind.Socket)
transp.fd = sock
transp.flags = flags
let size = max(bufsize, DefaultStreamBufferSize)
transp.buffer = BipBuffer.init(size)
transp.state = {ReadPaused, WritePaused}
@ -1535,21 +1595,30 @@ else:
retFuture.fail(getTransportOsError(error))
return retFuture
if localAddress != TransportAddress():
if localAddress.family != address.family:
sock.closeSocket()
retFuture.fail(newException(TransportOsError,
"connect local address domain is not equal to target address domain"))
return retFuture
let transportFlags =
block:
# Add `V4Mapped` flag when `::` address is used and dualstack is
# set to enabled or auto.
var res: set[TransportFlags]
if (localAddress.family == AddressFamily.IPv6) and
localAddress.isAnyLocal():
if dualstack != DualStackType.Disabled:
res.incl(TransportFlags.V4Mapped)
res
case localAddress.family
of AddressFamily.IPv4, AddressFamily.IPv6, AddressFamily.Unix:
var
localAddr: Sockaddr_storage
localAddrLen: SockLen
localAddress.toSAddr(localAddr, localAddrLen)
lsaddr: Sockaddr_storage
lslen: SockLen
toSAddr(localAddress, lsaddr, lslen)
if bindSocket(SocketHandle(sock),
cast[ptr SockAddr](addr localAddr), localAddrLen) != 0:
cast[ptr SockAddr](addr lsaddr), lslen) != 0:
sock.closeSocket()
retFuture.fail(getTransportOsError(osLastError()))
return retFuture
of AddressFamily.None:
discard
proc continuation(udata: pointer) =
if not(retFuture.finished()):
@ -1568,7 +1637,8 @@ else:
retFuture.fail(getTransportOsError(OSErrorCode(err)))
return
let transp = newStreamSocketTransport(sock, bufferSize, child)
let transp = newStreamSocketTransport(sock, bufferSize, child,
transportFlags)
# Start tracking transport
trackCounter(StreamTransportTrackerName)
retFuture.complete(transp)
@ -1581,7 +1651,8 @@ else:
let res = osdefs.connect(SocketHandle(sock),
cast[ptr SockAddr](addr saddr), slen)
if res == 0:
let transp = newStreamSocketTransport(sock, bufferSize, child)
let transp = newStreamSocketTransport(sock, bufferSize, child,
transportFlags)
# Start tracking transport
trackCounter(StreamTransportTrackerName)
retFuture.complete(transp)
@ -1634,9 +1705,11 @@ else:
let ntransp =
if not(isNil(server.init)):
let transp = server.init(server, sock)
newStreamSocketTransport(sock, server.bufferSize, transp)
newStreamSocketTransport(sock, server.bufferSize, transp,
server.getTransportFlags())
else:
newStreamSocketTransport(sock, server.bufferSize, nil)
newStreamSocketTransport(sock, server.bufferSize, nil,
server.getTransportFlags())
trackCounter(StreamTransportTrackerName)
asyncSpawn server.function(server, ntransp)
else:
@ -1724,9 +1797,11 @@ else:
let ntransp =
if not(isNil(server.init)):
let transp = server.init(server, sock)
newStreamSocketTransport(sock, server.bufferSize, transp)
newStreamSocketTransport(sock, server.bufferSize, transp,
server.getTransportFlags())
else:
newStreamSocketTransport(sock, server.bufferSize, nil)
newStreamSocketTransport(sock, server.bufferSize, nil,
server.getTransportFlags())
# Start tracking transport
trackCounter(StreamTransportTrackerName)
retFuture.complete(ntransp)
@ -1879,166 +1954,196 @@ proc createStreamServer*(host: TransportAddress,
## ``child`` - existing object ``StreamServer``object to initialize, can be
## used to initalize ``StreamServer`` inherited objects.
## ``udata`` - user-defined pointer.
var
saddr: Sockaddr_storage
slen: SockLen
serverSocket: AsyncFD
localAddress: TransportAddress
let (serverSocket, localAddress, serverFlags) =
when defined(windows):
# Windows
if host.family in {AddressFamily.IPv4, AddressFamily.IPv6}:
var
saddr: Sockaddr_storage
slen: SockLen
laddress: TransportAddress
when defined(nimdoc):
discard
elif defined(windows):
# Windows
if host.family in {AddressFamily.IPv4, AddressFamily.IPv6}:
serverSocket =
let sockres =
if sock == asyncInvalidSocket:
# TODO (cheatfate): `valueOr` generates weird compile error.
let res = createAsyncSocket2(host.getDomain(), SockType.SOCK_STREAM,
Protocol.IPPROTO_TCP)
if res.isErr():
raiseTransportOsError(res.error())
res.get()
else:
setDescriptorBlocking(SocketHandle(sock), false).isOkOr:
raiseTransportOsError(error)
register2(sock).isOkOr:
raiseTransportOsError(error)
sock
# SO_REUSEADDR
if ServerFlags.ReuseAddr in flags:
setSockOpt2(sockres, SOL_SOCKET, SO_REUSEADDR, 1).isOkOr:
if sock == asyncInvalidSocket:
discard closeFd(SocketHandle(sockres))
raiseTransportOsError(error)
# SO_REUSEPORT
if ServerFlags.ReusePort in flags:
setSockOpt2(sockres, SOL_SOCKET, SO_REUSEPORT, 1).isOkOr:
if sock == asyncInvalidSocket:
discard closeFd(SocketHandle(sockres))
raiseTransportOsError(error)
# TCP_NODELAY
if ServerFlags.TcpNoDelay in flags:
setSockOpt2(sockres, osdefs.IPPROTO_TCP,
osdefs.TCP_NODELAY, 1).isOkOr:
if sock == asyncInvalidSocket:
discard closeFd(SocketHandle(sockres))
raiseTransportOsError(error)
# IPV6_V6ONLY.
if sock == asyncInvalidSocket:
setDualstack(sockres, host.family, dualstack).isOkOr:
discard closeFd(SocketHandle(sockres))
raiseTransportOsError(error)
else:
setDualstack(sockres, dualstack).isOkOr:
raiseTransportOsError(error)
let flagres =
block:
var res = flags
if (host.family == AddressFamily.IPv6) and host.isAnyLocal():
if dualstack in {DualStackType.Enabled, DualStackType.Auto}:
res.incl(ServerFlags.V4Mapped)
res
host.toSAddr(saddr, slen)
if bindSocket(SocketHandle(sockres),
cast[ptr SockAddr](addr saddr), slen) != 0:
let err = osLastError()
if sock == asyncInvalidSocket:
discard closeFd(SocketHandle(sockres))
raiseTransportOsError(err)
slen = SockLen(sizeof(saddr))
if getsockname(SocketHandle(sockres), cast[ptr SockAddr](addr saddr),
addr slen) != 0:
let err = osLastError()
if sock == asyncInvalidSocket:
discard closeFd(SocketHandle(sockres))
raiseTransportOsError(err)
fromSAddr(addr saddr, slen, laddress)
if listen(SocketHandle(sockres), getBacklogSize(backlog)) != 0:
let err = osLastError()
if sock == asyncInvalidSocket:
discard closeFd(SocketHandle(sockres))
raiseTransportOsError(err)
(sockres, laddress, flagres)
elif host.family == AddressFamily.Unix:
(AsyncFD(0), host, flags)
else:
raiseAssert "Incorrect host address family"
else:
# Posix
var
saddr: Sockaddr_storage
slen: SockLen
laddress: TransportAddress
let sockres =
if sock == asyncInvalidSocket:
let proto = if host.family == AddressFamily.Unix:
Protocol.IPPROTO_IP
else:
Protocol.IPPROTO_TCP
# TODO (cheatfate): `valueOr` generates weird compile error.
let res = createAsyncSocket2(host.getDomain(), SockType.SOCK_STREAM,
Protocol.IPPROTO_TCP)
proto)
if res.isErr():
raiseTransportOsError(res.error())
res.get()
else:
setDescriptorBlocking(SocketHandle(sock), false).isOkOr:
setDescriptorFlags(cint(sock), true, true).isOkOr:
raiseTransportOsError(error)
register2(sock).isOkOr:
raiseTransportOsError(error)
sock
# SO_REUSEADDR
if ServerFlags.ReuseAddr in flags:
setSockOpt2(serverSocket, SOL_SOCKET, SO_REUSEADDR, 1).isOkOr:
if sock == asyncInvalidSocket:
discard closeFd(SocketHandle(serverSocket))
raiseTransportOsError(error)
# SO_REUSEPORT
if ServerFlags.ReusePort in flags:
setSockOpt2(serverSocket, SOL_SOCKET, SO_REUSEPORT, 1).isOkOr:
if sock == asyncInvalidSocket:
discard closeFd(SocketHandle(serverSocket))
raiseTransportOsError(error)
# TCP_NODELAY
if ServerFlags.TcpNoDelay in flags:
setSockOpt2(serverSocket, osdefs.IPPROTO_TCP,
osdefs.TCP_NODELAY, 1).isOkOr:
if sock == asyncInvalidSocket:
discard closeFd(SocketHandle(serverSocket))
raiseTransportOsError(error)
# IPV6_V6ONLY.
if sock == asyncInvalidSocket:
setDualstack(serverSocket, host.family, dualstack).isOkOr:
discard closeFd(SocketHandle(serverSocket))
raiseTransportOsError(error)
else:
setDualstack(serverSocket, dualstack).isOkOr:
raiseTransportOsError(error)
if host.family in {AddressFamily.IPv4, AddressFamily.IPv6}:
# SO_REUSEADDR
if ServerFlags.ReuseAddr in flags:
setSockOpt2(sockres, SOL_SOCKET, SO_REUSEADDR, 1).isOkOr:
if sock == asyncInvalidSocket:
discard unregisterAndCloseFd(sockres)
raiseTransportOsError(error)
# SO_REUSEPORT
if ServerFlags.ReusePort in flags:
setSockOpt2(sockres, SOL_SOCKET, SO_REUSEPORT, 1).isOkOr:
if sock == asyncInvalidSocket:
discard unregisterAndCloseFd(sockres)
raiseTransportOsError(error)
# TCP_NODELAY
if ServerFlags.TcpNoDelay in flags:
setSockOpt2(sockres, osdefs.IPPROTO_TCP,
osdefs.TCP_NODELAY, 1).isOkOr:
if sock == asyncInvalidSocket:
discard unregisterAndCloseFd(sockres)
raiseTransportOsError(error)
# IPV6_V6ONLY
if sock == asyncInvalidSocket:
setDualstack(sockres, host.family, dualstack).isOkOr:
discard closeFd(SocketHandle(sockres))
raiseTransportOsError(error)
else:
setDualstack(sockres, dualstack).isOkOr:
raiseTransportOsError(error)
elif host.family in {AddressFamily.Unix}:
# We do not care about result here, because if file cannot be removed,
# `bindSocket` will return EADDRINUSE.
discard osdefs.unlink(cast[cstring](baseAddr host.address_un))
let flagres =
block:
var res = flags
if (host.family == AddressFamily.IPv6) and host.isAnyLocal():
if dualstack != DualStackType.Disabled:
res.incl(ServerFlags.V4Mapped)
res
host.toSAddr(saddr, slen)
if bindSocket(SocketHandle(serverSocket),
cast[ptr SockAddr](addr saddr), slen) != 0:
if osdefs.bindSocket(SocketHandle(sockres),
cast[ptr SockAddr](addr saddr), slen) != 0:
let err = osLastError()
if sock == asyncInvalidSocket:
discard closeFd(SocketHandle(serverSocket))
discard unregisterAndCloseFd(sockres)
raiseTransportOsError(err)
# Obtain real address
slen = SockLen(sizeof(saddr))
if getsockname(SocketHandle(serverSocket), cast[ptr SockAddr](addr saddr),
if getsockname(SocketHandle(sockres), cast[ptr SockAddr](addr saddr),
addr slen) != 0:
let err = osLastError()
if sock == asyncInvalidSocket:
discard closeFd(SocketHandle(serverSocket))
discard unregisterAndCloseFd(sockres)
raiseTransportOsError(err)
fromSAddr(addr saddr, slen, localAddress)
if listen(SocketHandle(serverSocket), getBacklogSize(backlog)) != 0:
fromSAddr(addr saddr, slen, laddress)
if listen(SocketHandle(sockres), getBacklogSize(backlog)) != 0:
let err = osLastError()
if sock == asyncInvalidSocket:
discard closeFd(SocketHandle(serverSocket))
discard unregisterAndCloseFd(sockres)
raiseTransportOsError(err)
elif host.family == AddressFamily.Unix:
serverSocket = AsyncFD(0)
else:
# Posix
serverSocket =
if sock == asyncInvalidSocket:
let proto = if host.family == AddressFamily.Unix:
Protocol.IPPROTO_IP
else:
Protocol.IPPROTO_TCP
# TODO (cheatfate): `valueOr` generates weird compile error.
let res = createAsyncSocket2(host.getDomain(), SockType.SOCK_STREAM,
proto)
if res.isErr():
raiseTransportOsError(res.error())
res.get()
else:
setDescriptorFlags(cint(sock), true, true).isOkOr:
raiseTransportOsError(error)
register2(sock).isOkOr:
raiseTransportOsError(error)
sock
if host.family in {AddressFamily.IPv4, AddressFamily.IPv6}:
# SO_REUSEADDR
if ServerFlags.ReuseAddr in flags:
setSockOpt2(serverSocket, SOL_SOCKET, SO_REUSEADDR, 1).isOkOr:
if sock == asyncInvalidSocket:
discard unregisterAndCloseFd(serverSocket)
raiseTransportOsError(error)
# SO_REUSEPORT
if ServerFlags.ReusePort in flags:
setSockOpt2(serverSocket, SOL_SOCKET, SO_REUSEPORT, 1).isOkOr:
if sock == asyncInvalidSocket:
discard unregisterAndCloseFd(serverSocket)
raiseTransportOsError(error)
# TCP_NODELAY
if ServerFlags.TcpNoDelay in flags:
setSockOpt2(serverSocket, osdefs.IPPROTO_TCP,
osdefs.TCP_NODELAY, 1).isOkOr:
if sock == asyncInvalidSocket:
discard unregisterAndCloseFd(serverSocket)
raiseTransportOsError(error)
# IPV6_V6ONLY
if sock == asyncInvalidSocket:
setDualstack(serverSocket, host.family, dualstack).isOkOr:
discard closeFd(SocketHandle(serverSocket))
raiseTransportOsError(error)
else:
setDualstack(serverSocket, dualstack).isOkOr:
raiseTransportOsError(error)
elif host.family in {AddressFamily.Unix}:
# We do not care about result here, because if file cannot be removed,
# `bindSocket` will return EADDRINUSE.
discard osdefs.unlink(cast[cstring](baseAddr host.address_un))
host.toSAddr(saddr, slen)
if osdefs.bindSocket(SocketHandle(serverSocket),
cast[ptr SockAddr](addr saddr), slen) != 0:
let err = osLastError()
if sock == asyncInvalidSocket:
discard unregisterAndCloseFd(serverSocket)
raiseTransportOsError(err)
# Obtain real address
slen = SockLen(sizeof(saddr))
if getsockname(SocketHandle(serverSocket), cast[ptr SockAddr](addr saddr),
addr slen) != 0:
let err = osLastError()
if sock == asyncInvalidSocket:
discard unregisterAndCloseFd(serverSocket)
raiseTransportOsError(err)
fromSAddr(addr saddr, slen, localAddress)
if listen(SocketHandle(serverSocket), getBacklogSize(backlog)) != 0:
let err = osLastError()
if sock == asyncInvalidSocket:
discard unregisterAndCloseFd(serverSocket)
raiseTransportOsError(err)
(sockres, laddress, flagres)
var sres = if not(isNil(child)): child else: StreamServer()
sres.sock = serverSocket
sres.flags = flags
sres.flags = serverFlags
sres.function = cbproc
sres.init = init
sres.bufferSize = bufferSize
@ -2048,9 +2153,7 @@ proc createStreamServer*(host: TransportAddress,
{FutureFlag.OwnCancelSchedule})
sres.udata = udata
sres.dualstack = dualstack
if localAddress.family == AddressFamily.None:
sres.local = host
else:
if localAddress.family != AddressFamily.None:
sres.local = localAddress
when defined(windows):
@ -2115,6 +2218,52 @@ proc createStreamServer*(host: TransportAddress,
createStreamServer(host, StreamCallback2(nil), flags, sock, backlog, bufferSize,
child, init, cast[pointer](udata), dualstack)
proc createStreamServer*(port: Port,
host: Opt[IpAddress] = Opt.none(IpAddress),
flags: set[ServerFlags] = {},
sock: AsyncFD = asyncInvalidSocket,
backlog: int = DefaultBacklogSize,
bufferSize: int = DefaultStreamBufferSize,
child: StreamServer = nil,
init: TransportInitCallback = nil,
udata: pointer = nil,
dualstack = DualStackType.Auto): StreamServer {.
raises: [TransportOsError].} =
## Create stream server which will be bound to:
## 1. IPv6 address `::`, if IPv6 is available
## 2. IPv4 address `0.0.0.0`, if IPv6 is not available.
let hostname =
if host.isSome():
initTAddress(host.get(), port)
else:
getAutoAddress(port)
createStreamServer(hostname, StreamCallback2(nil), flags, sock,
backlog, bufferSize, child, init, cast[pointer](udata),
dualstack)
proc createStreamServer*(cbproc: StreamCallback2,
port: Port,
host: Opt[IpAddress] = Opt.none(IpAddress),
flags: set[ServerFlags] = {},
sock: AsyncFD = asyncInvalidSocket,
backlog: int = DefaultBacklogSize,
bufferSize: int = DefaultStreamBufferSize,
child: StreamServer = nil,
init: TransportInitCallback = nil,
udata: pointer = nil,
dualstack = DualStackType.Auto): StreamServer {.
raises: [TransportOsError].} =
## Create stream server which will be bound to:
## 1. IPv6 address `::`, if IPv6 is available
## 2. IPv4 address `0.0.0.0`, if IPv6 is not available.
let hostname =
if host.isSome():
initTAddress(host.get(), port)
else:
getAutoAddress(port)
createStreamServer(hostname, cbproc, flags, sock, backlog,
bufferSize, child, init, cast[pointer](udata), dualstack)
proc createStreamServer*[T](host: TransportAddress,
cbproc: StreamCallback2,
flags: set[ServerFlags] = {},
@ -2163,6 +2312,56 @@ proc createStreamServer*[T](host: TransportAddress,
createStreamServer(host, StreamCallback2(nil), fflags, sock, backlog, bufferSize,
child, init, cast[pointer](udata), dualstack)
proc createStreamServer*[T](cbproc: StreamCallback2,
port: Port,
host: Opt[IpAddress] = Opt.none(IpAddress),
flags: set[ServerFlags] = {},
udata: ref T,
sock: AsyncFD = asyncInvalidSocket,
backlog: int = DefaultBacklogSize,
bufferSize: int = DefaultStreamBufferSize,
child: StreamServer = nil,
init: TransportInitCallback = nil,
dualstack = DualStackType.Auto): StreamServer {.
raises: [TransportOsError].} =
## Create stream server which will be bound to:
## 1. IPv6 address `::`, if IPv6 is available
## 2. IPv4 address `0.0.0.0`, if IPv6 is not available.
let fflags = flags + {GCUserData}
GC_ref(udata)
let hostname =
if host.isSome():
initTAddress(host.get(), port)
else:
getAutoAddress(port)
createStreamServer(hostname, cbproc, fflags, sock, backlog,
bufferSize, child, init, cast[pointer](udata), dualstack)
proc createStreamServer*[T](port: Port,
host: Opt[IpAddress] = Opt.none(IpAddress),
flags: set[ServerFlags] = {},
udata: ref T,
sock: AsyncFD = asyncInvalidSocket,
backlog: int = DefaultBacklogSize,
bufferSize: int = DefaultStreamBufferSize,
child: StreamServer = nil,
init: TransportInitCallback = nil,
dualstack = DualStackType.Auto): StreamServer {.
raises: [TransportOsError].} =
## Create stream server which will be bound to:
## 1. IPv6 address `::`, if IPv6 is available
## 2. IPv4 address `0.0.0.0`, if IPv6 is not available.
let fflags = flags + {GCUserData}
GC_ref(udata)
let hostname =
if host.isSome():
initTAddress(host.get(), port)
else:
getAutoAddress(port)
createStreamServer(hostname, StreamCallback2(nil), fflags, sock,
backlog, bufferSize, child, init, cast[pointer](udata),
dualstack)
proc getUserData*[T](server: StreamServer): T {.inline.} =
## Obtain user data stored in ``server`` object.
cast[T](server.udata)

View File

@ -32,6 +32,10 @@ suite "Datagram Transport test suite":
m8 = "Bounded multiple clients with messages (" & $ClientsCount &
" clients x " & $MessagesCount & " messages)"
type
DatagramSocketType {.pure.} = enum
Bound, Unbound
proc client1(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.async: (raises: []).} =
try:
@ -628,6 +632,243 @@ suite "Datagram Transport test suite":
await allFutures(sdgram.closeWait(), cdgram.closeWait())
res == 1
proc performAutoAddressTest(port: Port,
family: AddressFamily): Future[bool] {.async.} =
var
expectRequest1 = "AUTO REQUEST1"
expectRequest2 = "AUTO REQUEST2"
expectResponse = "AUTO RESPONSE"
mappedResponse = "MAPPED RESPONSE"
event = newAsyncEvent()
event2 = newAsyncEvent()
res = 0
proc process1(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.
async: (raises: []).} =
try:
var
bmsg = transp.getMessage()
smsg = string.fromBytes(bmsg)
if smsg == expectRequest1:
inc(res)
await noCancel transp.sendTo(
raddr, addr expectResponse[0], len(expectResponse))
elif smsg == expectRequest2:
inc(res)
await noCancel transp.sendTo(
raddr, addr mappedResponse[0], len(mappedResponse))
except TransportError as exc:
raiseAssert exc.msg
except CancelledError as exc:
raiseAssert exc.msg
proc process2(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.
async: (raises: []).} =
try:
var
bmsg = transp.getMessage()
smsg = string.fromBytes(bmsg)
if smsg == expectResponse:
inc(res)
event.fire()
except TransportError as exc:
raiseAssert exc.msg
except CancelledError as exc:
raiseAssert exc.msg
proc process3(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.
async: (raises: []).} =
try:
var
bmsg = transp.getMessage()
smsg = string.fromBytes(bmsg)
if smsg == mappedResponse:
inc(res)
event2.fire()
except TransportError as exc:
raiseAssert exc.msg
except CancelledError as exc:
raiseAssert exc.msg
let sdgram =
block:
var res: DatagramTransport
var currentPort = port
for i in 0 ..< 10:
res =
try:
newDatagramTransport(process1, currentPort,
flags = {ServerFlags.ReusePort})
except TransportOsError:
echo "Unable to create transport on port ", currentPort
currentPort = Port(uint16(currentPort) + 1'u16)
nil
if not(isNil(res)):
break
doAssert(not(isNil(res)), "Unable to create transport, giving up")
res
var
address =
case family
of AddressFamily.IPv4:
initTAddress("127.0.0.1:0")
of AddressFamily.IPv6:
initTAddress("::1:0")
of AddressFamily.Unix, AddressFamily.None:
raiseAssert "Not allowed"
let
cdgram =
case family
of AddressFamily.IPv4:
newDatagramTransport(process2, local = address)
of AddressFamily.IPv6:
newDatagramTransport6(process2, local = address)
of AddressFamily.Unix, AddressFamily.None:
raiseAssert "Not allowed"
address.port = sdgram.localAddress().port
try:
await noCancel cdgram.sendTo(
address, addr expectRequest1[0], len(expectRequest1))
except TransportError:
discard
if family == AddressFamily.IPv6:
var remote = initTAddress("127.0.0.1:0")
remote.port = sdgram.localAddress().port
let wtransp =
newDatagramTransport(process3, local = initTAddress("0.0.0.0:0"))
try:
await noCancel wtransp.sendTo(
remote, addr expectRequest2[0], len(expectRequest2))
except TransportError as exc:
raiseAssert "Got transport error, reason = " & $exc.msg
try:
await event2.wait().wait(1.seconds)
except CatchableError:
discard
await wtransp.closeWait()
try:
await event.wait().wait(1.seconds)
except CatchableError:
discard
await allFutures(sdgram.closeWait(), cdgram.closeWait())
if family == AddressFamily.IPv4:
res == 2
else:
res == 4
proc performAutoAddressTest2(
address1: Opt[IpAddress],
address2: Opt[IpAddress],
port: Port,
sendType: AddressFamily,
boundType: DatagramSocketType
): Future[bool] {.async.} =
let
expectRequest = "TEST REQUEST"
expectResponse = "TEST RESPONSE"
event = newAsyncEvent()
var res = 0
proc process1(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.
async: (raises: []).} =
if raddr.family != sendType:
raiseAssert "Incorrect address family received [" & $raddr &
"], expected [" & $sendType & "]"
try:
let
bmsg = transp.getMessage()
smsg = string.fromBytes(bmsg)
if smsg == expectRequest:
inc(res)
await noCancel transp.sendTo(
raddr, unsafeAddr expectResponse[0], len(expectResponse))
except TransportError as exc:
raiseAssert exc.msg
except CancelledError as exc:
raiseAssert exc.msg
proc process2(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.
async: (raises: []).} =
if raddr.family != sendType:
raiseAssert "Incorrect address family received [" & $raddr &
"], expected [" & $sendType & "]"
try:
let
bmsg = transp.getMessage()
smsg = string.fromBytes(bmsg)
if smsg == expectResponse:
inc(res)
event.fire()
except TransportError as exc:
raiseAssert exc.msg
except CancelledError as exc:
raiseAssert exc.msg
let
serverFlags = {ServerFlags.ReuseAddr}
server = newDatagramTransport(process1, flags = serverFlags,
local = address1, localPort = port)
serverAddr = server.localAddress()
serverPort = serverAddr.port
remoteAddress =
case sendType
of AddressFamily.IPv4:
var res = initTAddress("127.0.0.1:0")
res.port = serverPort
res
of AddressFamily.IPv6:
var res = initTAddress("[::1]:0")
res.port = serverPort
res
else:
raiseAssert "Incorrect sending type"
remoteIpAddress = Opt.some(remoteAddress.toIpAddress())
client =
case boundType
of DatagramSocketType.Bound:
newDatagramTransport(process2,
localPort = Port(0), remotePort = serverPort,
local = address2, remote = remoteIpAddress)
of DatagramSocketType.Unbound:
newDatagramTransport(process2,
localPort = Port(0), remotePort = Port(0),
local = address2)
try:
case boundType
of DatagramSocketType.Bound:
await noCancel client.send(
unsafeAddr expectRequest[0], len(expectRequest))
of DatagramSocketType.Unbound:
await noCancel client.sendTo(remoteAddress,
unsafeAddr expectRequest[0], len(expectRequest))
except TransportError as exc:
raiseAssert "Could not send datagram to remote peer, reason = " & $exc.msg
try:
await event.wait().wait(1.seconds)
except CatchableError:
discard
await allFutures(server.closeWait(), client.closeWait())
res == 2
test "close(transport) test":
check waitFor(testTransportClose()) == true
test m1:
@ -730,3 +971,104 @@ suite "Datagram Transport test suite":
DualStackType.Auto, initTAddress("[::1]:0"))) == true
else:
skip()
asyncTest "[IP] Auto-address constructor test (*:0)":
if isAvailable(AddressFamily.IPv6):
check:
(await performAutoAddressTest(Port(0), AddressFamily.IPv6)) == true
# If IPv6 is available newAutoDatagramTransport should bind to `::` - this
# means that we should be able to connect to it via IPV4_MAPPED address,
# but only when IPv4 is also available.
if isAvailable(AddressFamily.IPv4):
check:
(await performAutoAddressTest(Port(0), AddressFamily.IPv4)) == true
else:
# If IPv6 is not available newAutoDatagramTransport should bind to
# `0.0.0.0` - this means we should be able to connect to it via IPv4
# address.
if isAvailable(AddressFamily.IPv4):
check:
(await performAutoAddressTest(Port(0), AddressFamily.IPv4)) == true
asyncTest "[IP] Auto-address constructor test (*:30231)":
if isAvailable(AddressFamily.IPv6):
check:
(await performAutoAddressTest(Port(30231), AddressFamily.IPv6)) == true
# If IPv6 is available newAutoDatagramTransport should bind to `::` - this
# means that we should be able to connect to it via IPV4_MAPPED address,
# but only when IPv4 is also available.
if isAvailable(AddressFamily.IPv4):
check:
(await performAutoAddressTest(Port(30231), AddressFamily.IPv4)) ==
true
else:
# If IPv6 is not available newAutoDatagramTransport should bind to
# `0.0.0.0` - this means we should be able to connect to it via IPv4
# address.
if isAvailable(AddressFamily.IPv4):
check:
(await performAutoAddressTest(Port(30231), AddressFamily.IPv4)) ==
true
for socketType in DatagramSocketType:
for portNumber in [Port(0), Port(30231)]:
asyncTest "[IP] IPv6 mapping test (" & $socketType &
"/auto-auto:" & $int(portNumber) & ")":
if isAvailable(AddressFamily.IPv6):
let
address1 = Opt.none(IpAddress)
address2 = Opt.none(IpAddress)
check:
(await performAutoAddressTest2(
address1, address2, portNumber, AddressFamily.IPv4, socketType))
(await performAutoAddressTest2(
address1, address2, portNumber, AddressFamily.IPv6, socketType))
else:
skip()
asyncTest "[IP] IPv6 mapping test (" & $socketType &
"/auto-ipv6:" & $int(portNumber) & ")":
if isAvailable(AddressFamily.IPv6):
let
address1 = Opt.none(IpAddress)
address2 = Opt.some(initTAddress("[::1]:0").toIpAddress())
check:
(await performAutoAddressTest2(
address1, address2, portNumber, AddressFamily.IPv6, socketType))
else:
skip()
asyncTest "[IP] IPv6 mapping test (" & $socketType &
"/auto-ipv4:" & $int(portNumber) & ")":
if isAvailable(AddressFamily.IPv6):
let
address1 = Opt.none(IpAddress)
address2 = Opt.some(initTAddress("127.0.0.1:0").toIpAddress())
check:
(await performAutoAddressTest2(address1, address2, portNumber,
AddressFamily.IPv4, socketType))
else:
skip()
asyncTest "[IP] IPv6 mapping test (" & $socketType &
"/ipv6-auto:" & $int(portNumber) & ")":
if isAvailable(AddressFamily.IPv6):
let
address1 = Opt.some(initTAddress("[::1]:0").toIpAddress())
address2 = Opt.none(IpAddress)
check:
(await performAutoAddressTest2(address1, address2, portNumber,
AddressFamily.IPv6, socketType))
else:
skip()
asyncTest "[IP] IPv6 mapping test (" & $socketType &
"/ipv4-auto:" & $int(portNumber) & ")":
if isAvailable(AddressFamily.IPv6):
let
address1 = Opt.some(initTAddress("127.0.0.1:0").toIpAddress())
address2 = Opt.none(IpAddress)
check:
(await performAutoAddressTest2(address1, address2, portNumber,
AddressFamily.IPv4, socketType))
else:
skip()

View File

@ -1486,6 +1486,170 @@ suite "Stream Transport test suite":
await server.closeWait()
testResult
proc performAutoAddressTest(port: Port,
family: AddressFamily): Future[bool] {.
async: (raises: []).} =
let server =
block:
var currentPort = port
var res: StreamServer
for i in 0 ..< 10:
res =
try:
createStreamServer(port, flags = {ServerFlags.ReuseAddr})
except TransportOsError as exc:
echo "Unable to create server on port ", currentPort,
" with error: ", exc.msg
currentPort = Port(uint16(currentPort) + 1'u16)
nil
if not(isNil(res)):
break
doAssert(not(isNil(res)), "Unable to create server, giving up")
res
var
address =
case family
of AddressFamily.IPv4:
try:
initTAddress("127.0.0.1:0")
except TransportAddressError as exc:
raiseAssert exc.msg
of AddressFamily.IPv6:
try:
initTAddress("::1:0")
except TransportAddressError as exc:
raiseAssert exc.msg
of AddressFamily.Unix, AddressFamily.None:
raiseAssert "Not allowed"
address.port = server.localAddress().port
var acceptFut = server.accept()
let
clientTransp =
try:
let res = await connect(address).wait(2.seconds)
Opt.some(res)
except CatchableError:
Opt.none(StreamTransport)
serverTransp =
if clientTransp.isSome():
let res =
try:
await noCancel acceptFut
except TransportError as exc:
raiseAssert exc.msg
Opt.some(res)
else:
Opt.none(StreamTransport)
let testResult = clientTransp.isSome() and serverTransp.isSome()
var pending: seq[FutureBase]
if clientTransp.isSome():
pending.add(closeWait(clientTransp.get()))
if serverTransp.isSome():
pending.add(closeWait(serverTransp.get()))
else:
pending.add(cancelAndWait(acceptFut))
await noCancel allFutures(pending)
try:
server.stop()
except TransportError as exc:
raiseAssert exc.msg
await server.closeWait()
testResult
proc performAutoAddressTest2(
address1: Opt[IpAddress],
address2: Opt[IpAddress],
port: Port,
sendType: AddressFamily
): Future[bool] {.async: (raises: []).} =
let
server =
block:
var
currentPort = port
res: StreamServer
for i in 0 ..< 10:
res =
try:
createStreamServer(port, host = address1,
flags = {ServerFlags.ReuseAddr})
except TransportOsError as exc:
echo "Unable to create server on port ", currentPort,
" with error: ", exc.msg
currentPort = Port(uint16(currentPort) + 1'u16)
nil
if not(isNil(res)):
break
doAssert(not(isNil(res)), "Unable to create server, giving up")
res
serverAddr = server.localAddress()
serverPort = serverAddr.port
remoteAddress =
try:
case sendType
of AddressFamily.IPv4:
var res = initTAddress("127.0.0.1:0")
res.port = serverPort
res
of AddressFamily.IPv6:
var res = initTAddress("[::1]:0")
res.port = serverPort
res
else:
raiseAssert "Incorrect sending type"
except TransportAddressError as exc:
raiseAssert "Unable to initialize transport address, " &
"reason = " & exc.msg
acceptFut = server.accept()
let
clientTransp =
try:
if address2.isSome():
let
laddr = initTAddress(address2.get(), Port(0))
res = await connect(remoteAddress, localAddress = laddr).
wait(2.seconds)
Opt.some(res)
else:
let res = await connect(remoteAddress).wait(2.seconds)
Opt.some(res)
except CatchableError:
Opt.none(StreamTransport)
serverTransp =
if clientTransp.isSome():
let res =
try:
await noCancel acceptFut
except TransportError as exc:
raiseAssert exc.msg
Opt.some(res)
else:
Opt.none(StreamTransport)
testResult =
clientTransp.isSome() and serverTransp.isSome() and
(serverTransp.get().remoteAddress2().get().family == sendType) and
(clientTransp.get().remoteAddress2().get().family == sendType)
var pending: seq[FutureBase]
if clientTransp.isSome():
pending.add(closeWait(clientTransp.get()))
if serverTransp.isSome():
pending.add(closeWait(serverTransp.get()))
else:
pending.add(cancelAndWait(acceptFut))
await noCancel allFutures(pending)
try:
server.stop()
except TransportError as exc:
raiseAssert exc.msg
await server.closeWait()
testResult
markFD = getCurrentFD()
for i in 0..<len(addresses):
@ -1668,6 +1832,96 @@ suite "Stream Transport test suite":
DualStackType.Disabled, initTAddress("[::1]:0"))) == true
else:
skip()
asyncTest "[IP] Auto-address constructor test (*:0)":
if isAvailable(AddressFamily.IPv6):
check:
(await performAutoAddressTest(Port(0), AddressFamily.IPv6)) == true
# If IPv6 is available createStreamServer should bind to `::` this means
# that we should be able to connect to it via IPV4_MAPPED address, but
# only when IPv4 is also available.
if isAvailable(AddressFamily.IPv4):
check:
(await performAutoAddressTest(Port(0), AddressFamily.IPv4)) == true
else:
# If IPv6 is not available createStreamServer should bind to `0.0.0.0`
# this means we should be able to connect to it via IPV4 address.
if isAvailable(AddressFamily.IPv4):
check:
(await performAutoAddressTest(Port(0), AddressFamily.IPv4)) == true
asyncTest "[IP] Auto-address constructor test (*:30532)":
if isAvailable(AddressFamily.IPv6):
check:
(await performAutoAddressTest(Port(30532), AddressFamily.IPv6)) == true
# If IPv6 is available createStreamServer should bind to `::` this means
# that we should be able to connect to it via IPV4_MAPPED address, but
# only when IPv4 is also available.
if isAvailable(AddressFamily.IPv4):
check:
(await performAutoAddressTest(Port(30532), AddressFamily.IPv4)) ==
true
else:
# If IPv6 is not available createStreamServer should bind to `0.0.0.0`
# this means we should be able to connect to it via IPV4 address.
if isAvailable(AddressFamily.IPv4):
check:
(await performAutoAddressTest(Port(30532), AddressFamily.IPv4)) ==
true
for portNumber in [Port(0), Port(30231)]:
asyncTest "[IP] IPv6 mapping test (auto-auto:" & $int(portNumber) & ")":
if isAvailable(AddressFamily.IPv6):
let
address1 = Opt.none(IpAddress)
address2 = Opt.none(IpAddress)
check:
(await performAutoAddressTest2(
address1, address2, portNumber, AddressFamily.IPv4))
(await performAutoAddressTest2(
address1, address2, portNumber, AddressFamily.IPv6))
else:
skip()
asyncTest "[IP] IPv6 mapping test (auto-ipv6:" & $int(portNumber) & ")":
if isAvailable(AddressFamily.IPv6):
let
address1 = Opt.none(IpAddress)
address2 = Opt.some(initTAddress("[::1]:0").toIpAddress())
check:
(await performAutoAddressTest2(
address1, address2, portNumber, AddressFamily.IPv6))
else:
skip()
asyncTest "[IP] IPv6 mapping test (auto-ipv4:" & $int(portNumber) & ")":
if isAvailable(AddressFamily.IPv6):
let
address1 = Opt.none(IpAddress)
address2 = Opt.some(initTAddress("127.0.0.1:0").toIpAddress())
check:
(await performAutoAddressTest2(
address1, address2, portNumber, AddressFamily.IPv4))
else:
skip()
asyncTest "[IP] IPv6 mapping test (ipv6-auto:" & $int(portNumber) & ")":
if isAvailable(AddressFamily.IPv6):
let
address1 = Opt.some(initTAddress("[::1]:0").toIpAddress())
address2 = Opt.none(IpAddress)
check:
(await performAutoAddressTest2(
address1, address2, portNumber, AddressFamily.IPv6))
else:
skip()
asyncTest "[IP] IPv6 mapping test (ipv4-auto:" & $int(portNumber) & ")":
if isAvailable(AddressFamily.IPv6):
let
address1 = Opt.some(initTAddress("127.0.0.1:0").toIpAddress())
address2 = Opt.none(IpAddress)
check:
(await performAutoAddressTest2(
address1, address2, portNumber, AddressFamily.IPv4))
else:
skip()
test "File descriptors leak test":
when defined(windows):
# Windows handle numbers depends on many conditions, so we can't use