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:
parent
8e49df1400
commit
0d050d5823
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue