#
#
# Nim's Runtime Library
# (c) Copyright 2015 Dominik Picheta
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module implements a low-level cross-platform sockets interface. Look
## at the ``net`` module for the higher-level version.
# TODO: Clean up the exports a bit and everything else in general.
import os, options
when hostOS == "solaris":
{.passl: "-lsocket -lnsl".}
const useWinVersion = defined(Windows) or defined(nimdoc)
when useWinVersion:
import winlean
export WSAEWOULDBLOCK, WSAECONNRESET, WSAECONNABORTED, WSAENETRESET,
WSANOTINITIALISED, WSAENOTSOCK, WSAEINPROGRESS, WSAEINTR,
WSAEDISCON, ERROR_NETNAME_DELETED
else:
import posix
export fcntl, F_GETFL, O_NONBLOCK, F_SETFL, EAGAIN, EWOULDBLOCK, MSG_NOSIGNAL,
EINTR, EINPROGRESS, ECONNRESET, EPIPE, ENETRESET, EBADF
export Sockaddr_storage, Sockaddr_un, Sockaddr_un_path_length
export SocketHandle, Sockaddr_in, Addrinfo, INADDR_ANY, SockAddr, SockLen,
Sockaddr_in6, Sockaddr_storage,
inet_ntoa, recv, `==`, connect, send, accept, recvfrom, sendto,
freeAddrInfo
export
SO_ERROR,
SOL_SOCKET,
SOMAXCONN,
SO_ACCEPTCONN, SO_BROADCAST, SO_DEBUG, SO_DONTROUTE,
SO_KEEPALIVE, SO_OOBINLINE, SO_REUSEADDR, SO_REUSEPORT,
MSG_PEEK
when defined(macosx) and not defined(nimdoc):
export SO_NOSIGPIPE
type
Port* = distinct uint16 ## port type
Domain* = enum ## \
## domain, which specifies the protocol family of the
## created socket. Other domains than those that are listed
## here are unsupported.
AF_UNSPEC = 0, ## unspecified domain (can be detected automatically by
## some procedures, such as getaddrinfo)
AF_UNIX = 1, ## for local socket (using a file). Unsupported on Windows.
AF_INET = 2, ## for network protocol IPv4 or
AF_INET6 = when defined(macosx): 30 else: 23 ## for network protocol IPv6.
SockType* = enum ## second argument to `socket` proc
SOCK_STREAM = 1, ## reliable stream-oriented service or Stream Sockets
SOCK_DGRAM = 2, ## datagram service or Datagram Sockets
SOCK_RAW = 3, ## raw protocols atop the network layer.
SOCK_SEQPACKET = 5 ## reliable sequenced packet service
Protocol* = enum ## third argument to `socket` proc
IPPROTO_TCP = 6, ## Transmission control protocol.
IPPROTO_UDP = 17, ## User datagram protocol.
IPPROTO_IP, ## Internet protocol. Unsupported on Windows.
IPPROTO_IPV6, ## Internet Protocol Version 6. Unsupported on Windows.
IPPROTO_RAW, ## Raw IP Packets Protocol. Unsupported on Windows.
IPPROTO_ICMP ## Control message protocol. Unsupported on Windows.
IPPROTO_ICMPV6 ## Control message protocol for IPv6. Unsupported on Windows.
Servent* = object ## information about a service
name*: string
aliases*: seq[string]
port*: Port
proto*: string
Hostent* = object ## information about a given host
name*: string
aliases*: seq[string]
addrtype*: Domain
length*: int
addrList*: seq[string]
when useWinVersion:
let
osInvalidSocket* = winlean.INVALID_SOCKET
const
IOCPARM_MASK* = 127
IOC_IN* = int(-2147483648)
FIONBIO* = IOC_IN.int32 or ((sizeof(int32) and IOCPARM_MASK) shl 16) or
(102 shl 8) or 126
nativeAfInet = winlean.AF_INET
nativeAfInet6 = winlean.AF_INET6
proc ioctlsocket*(s: SocketHandle, cmd: clong,
argptr: ptr clong): cint {.
stdcall, importc: "ioctlsocket", dynlib: "ws2_32.dll".}
else:
let
osInvalidSocket* = posix.INVALID_SOCKET
nativeAfInet = posix.AF_INET
nativeAfInet6 = posix.AF_INET6
nativeAfUnix = posix.AF_UNIX
proc `==`*(a, b: Port): bool {.borrow.}
## ``==`` for ports.
proc `$`*(p: Port): string {.borrow.}
## returns the port number as a string
proc toInt*(domain: Domain): cint
## Converts the Domain enum to a platform-dependent ``cint``.
proc toInt*(typ: SockType): cint
## Converts the SockType enum to a platform-dependent ``cint``.
proc toInt*(p: Protocol): cint
## Converts the Protocol enum to a platform-dependent ``cint``.
when not useWinVersion:
proc toInt(domain: Domain): cint =
case domain
of AF_UNSPEC: result = posix.AF_UNSPEC.cint
of AF_UNIX: result = posix.AF_UNIX.cint
of AF_INET: result = posix.AF_INET.cint
of AF_INET6: result = posix.AF_INET6.cint
proc toKnownDomain*(family: cint): Option[Domain] =
## Converts the platform-dependent ``cint`` to the Domain or none(),
## if the ``cint`` is not known.
result = if family == posix.AF_UNSPEC: some(Domain.AF_UNSPEC)
elif family == posix.AF_UNIX: some(Domain.AF_UNIX)
elif family == posix.AF_INET: some(Domain.AF_INET)
elif family == posix.AF_INET6: some(Domain.AF_INET6)
else: none(Domain)
proc toInt(typ: SockType): cint =
case typ
of SOCK_STREAM: result = posix.SOCK_STREAM
of SOCK_DGRAM: result = posix.SOCK_DGRAM
of SOCK_SEQPACKET: result = posix.SOCK_SEQPACKET
of SOCK_RAW: result = posix.SOCK_RAW
proc toInt(p: Protocol): cint =
case p
of IPPROTO_TCP: result = posix.IPPROTO_TCP
of IPPROTO_UDP: result = posix.IPPROTO_UDP
of IPPROTO_IP: result = posix.IPPROTO_IP
of IPPROTO_IPV6: result = posix.IPPROTO_IPV6
of IPPROTO_RAW: result = posix.IPPROTO_RAW
of IPPROTO_ICMP: result = posix.IPPROTO_ICMP
of IPPROTO_ICMPV6: result = posix.IPPROTO_ICMPV6
else:
proc toInt(domain: Domain): cint =
result = toU32(ord(domain)).cint
proc toKnownDomain*(family: cint): Option[Domain] =
## Converts the platform-dependent ``cint`` to the Domain or none(),
## if the ``cint`` is not known.
result = if family == winlean.AF_UNSPEC: some(Domain.AF_UNSPEC)
elif family == winlean.AF_INET: some(Domain.AF_INET)
elif family == winlean.AF_INET6: some(Domain.AF_INET6)
else: none(Domain)
proc toInt(typ: SockType): cint =
result = cint(ord(typ))
proc toInt(p: Protocol): cint =
result = cint(ord(p))
proc toSockType*(protocol: Protocol): SockType =
result = case protocol
of IPPROTO_TCP:
SOCK_STREAM
of IPPROTO_UDP:
SOCK_DGRAM
of IPPROTO_IP, IPPROTO_IPV6, IPPROTO_RAW, IPPROTO_ICMP, IPPROTO_ICMPV6:
SOCK_RAW
proc close*(socket: SocketHandle) =
## closes a socket.
when useWinVersion:
discard winlean.closesocket(socket)
else:
discard posix.close(socket)
# TODO: These values should not be discarded. An OSError should be raised.
# http://stackoverflow.com/questions/12463473/what-happens-if-you-call-close-on-a-bsd-socket-multiple-times
when declared(setInheritable) or defined(nimdoc):
proc setInheritable*(s: SocketHandle, inheritable: bool): bool {.inline.} =
## Set whether a socket is inheritable by child processes. Returns `true`
## on success.
##
## This function is not implemented on all platform, test for availability
## with `declared() <system.html#declared,untyped>`.
setInheritable(FileHandle s, inheritable)
proc createNativeSocket*(domain: cint, sockType: cint, protocol: cint,
inheritable: bool = defined(nimInheritHandles)): SocketHandle =
## Creates a new socket; returns `osInvalidSocket` if an error occurs.
##
## `inheritable` decides if the resulting SocketHandle can be inherited
## by child processes.
##
## Use this overload if one of the enums specified above does
## not contain what you need.
let sockType =
when (defined(linux) or defined(bsd)) and not defined(nimdoc):
if inheritable: sockType and not SOCK_CLOEXEC else: sockType or SOCK_CLOEXEC
else:
sockType
result = socket(domain, sockType, protocol)
when declared(setInheritable) and not (defined(linux) or defined(bsd)):
if not setInheritable(result, inheritable):
close result
return osInvalidSocket
proc createNativeSocket*(domain: Domain = AF_INET,
sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP,
inheritable: bool = defined(nimInheritHandles)): SocketHandle =
## Creates a new socket; returns `osInvalidSocket` if an error occurs.
##
## `inheritable` decides if the resulting SocketHandle can be inherited
## by child processes.
createNativeSocket(toInt(domain), toInt(sockType), toInt(protocol))
proc newNativeSocket*(domain: Domain = AF_INET,
sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): SocketHandle
{.deprecated: "deprecated since v0.18.0; use 'createNativeSocket' instead".} =
## Creates a new socket; returns `osInvalidSocket` if an error occurs.
createNativeSocket(domain, sockType, protocol)
proc newNativeSocket*(domain: cint, sockType: cint,
protocol: cint): SocketHandle
{.deprecated: "deprecated since v0.18.0; use 'createNativeSocket' instead".} =
## Creates a new socket; returns `osInvalidSocket` if an error occurs.
##
## Use this overload if one of the enums specified above does
## not contain what you need.
createNativeSocket(domain, sockType, protocol)
proc bindAddr*(socket: SocketHandle, name: ptr SockAddr,
namelen: SockLen): cint =
result = bindSocket(socket, name, namelen)
proc listen*(socket: SocketHandle, backlog = SOMAXCONN): cint {.tags: [
ReadIOEffect].} =
## Marks ``socket`` as accepting connections.
## ``Backlog`` specifies the maximum length of the
## queue of pending connections.
when useWinVersion:
result = winlean.listen(socket, cint(backlog))
else:
result = posix.listen(socket, cint(backlog))
proc getAddrInfo*(address: string, port: Port, domain: Domain = AF_INET,
sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): ptr AddrInfo =
##
##
## **Warning**: The resulting ``ptr AddrInfo`` must be freed using ``freeAddrInfo``!
var hints: AddrInfo
result = nil
hints.ai_family = toInt(domain)
hints.ai_socktype = toInt(sockType)
hints.ai_protocol = toInt(protocol)
# OpenBSD doesn't support AI_V4MAPPED and doesn't define the macro AI_V4MAPPED.
# FreeBSD, Haiku don't support AI_V4MAPPED but defines the macro.
# https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=198092
# https://dev.haiku-os.org/ticket/14323
when not defined(freebsd) and not defined(openbsd) and not defined(netbsd) and
not defined(android) and not defined(haiku):
if domain == AF_INET6:
hints.ai_flags = AI_V4MAPPED
let socketPort = if sockType == SOCK_RAW: "" else: $port
var gaiResult = getaddrinfo(address, socketPort, addr(hints), result)
if gaiResult != 0'i32:
when useWinVersion:
raiseOSError(osLastError())
else:
raiseOSError(osLastError(), $gai_strerror(gaiResult))
proc ntohl*(x: uint32): uint32 =
## Converts 32-bit unsigned integers from network to host byte order.
## On machines where the host byte order is the same as network byte order,
## this is a no-op; otherwise, it performs a 4-byte swap operation.
when cpuEndian == bigEndian: result = x
else: result = (x shr 24'u32) or
(x shr 8'u32 and 0xff00'u32) or
(x shl 8'u32 and 0xff0000'u32) or
(x shl 24'u32)
proc ntohs*(x: uint16): uint16 =
## Converts 16-bit unsigned integers from network to host byte order. On
## machines where the host byte order is the same as network byte order,
## this is a no-op; otherwise, it performs a 2-byte swap operation.
when cpuEndian == bigEndian: result = x
else: result = (x shr 8'u16) or (x shl 8'u16)
template htonl*(x: uint32): untyped =
## Converts 32-bit unsigned integers from host to network byte order. On
## machines where the host byte order is the same as network byte order,
## this is a no-op; otherwise, it performs a 4-byte swap operation.
nativesockets.ntohl(x)
template htons*(x: uint16): untyped =
## Converts 16-bit unsigned integers from host to network byte order.
## On machines where the host byte order is the same as network byte
## order, this is a no-op; otherwise, it performs a 2-byte swap operation.
nativesockets.ntohs(x)
proc getServByName*(name, proto: string): Servent {.tags: [ReadIOEffect].} =
## Searches the database from the beginning and finds the first entry for
## which the service name specified by ``name`` matches the s_name member
## and the protocol name specified by ``proto`` matches the s_proto member.
##
## On posix this will search through the ``/etc/services`` file.
when useWinVersion:
var s = winlean.getservbyname(name, proto)
else:
var s = posix.getservbyname(name, proto)
if s == nil: raiseOSError(osLastError(), "Service not found.")
result.name = $s.s_name
result.aliases = cstringArrayToSeq(s.s_aliases)
result.port = Port(s.s_port)
result.proto = $s.s_proto
proc getServByPort*(port: Port, proto: string): Servent {.tags: [ReadIOEffect].} =
## Searches the database from the beginning and finds the first entry for
## which the port specified by ``port`` matches the s_port member and the
## protocol name specified by ``proto`` matches the s_proto member.
##
## On posix this will search through the ``/etc/services`` file.
when useWinVersion:
var s = winlean.getservbyport(ze(int16(port)).cint, proto)
else:
var s = posix.getservbyport(ze(int16(port)).cint, proto)
if s == nil: raiseOSError(osLastError(), "Service not found.")
result.name = $s.s_name
result.aliases = cstringArrayToSeq(s.s_aliases)
result.port = Port(s.s_port)
result.proto = $s.s_proto
proc getHostByAddr*(ip: string): Hostent {.tags: [ReadIOEffect].} =
## This function will lookup the hostname of an IP Address.
var myaddr: InAddr
myaddr.s_addr = inet_addr(ip)
when useWinVersion:
var s = winlean.gethostbyaddr(addr(myaddr), sizeof(myaddr).cuint,
cint(AF_INET))
if s == nil: raiseOSError(osLastError())
else:
var s =
when defined(android4):
posix.gethostbyaddr(cast[cstring](addr(myaddr)), sizeof(myaddr).cint,
cint(posix.AF_INET))
else:
posix.gethostbyaddr(addr(myaddr), sizeof(myaddr).SockLen,
cint(posix.AF_INET))
if s == nil:
raiseOSError(osLastError(), $hstrerror(h_errno))
result.name = $s.h_name
result.aliases = cstringArrayToSeq(s.h_aliases)
when useWinVersion:
result.addrtype = Domain(s.h_addrtype)
else:
if s.h_addrtype == posix.AF_INET:
result.addrtype = AF_INET
elif s.h_addrtype == posix.AF_INET6:
result.addrtype = AF_INET6
else:
raiseOSError(osLastError(), "unknown h_addrtype")
if result.addrtype == AF_INET:
result.addrList = @[]
var i = 0
while not isNil(s.h_addr_list[i]):
var inaddrPtr = cast[ptr InAddr](s.h_addr_list[i])
result.addrList.add($inet_ntoa(inaddrPtr[]))
inc(i)
else:
result.addrList = cstringArrayToSeq(s.h_addr_list)
result.length = int(s.h_length)
proc getHostByName*(name: string): Hostent {.tags: [ReadIOEffect].} =
## This function will lookup the IP address of a hostname.
when useWinVersion:
var s = winlean.gethostbyname(name)
else:
var s = posix.gethostbyname(name)
if s == nil: raiseOSError(osLastError())
result.name = $s.h_name
result.aliases = cstringArrayToSeq(s.h_aliases)
when useWinVersion:
result.addrtype = Domain(s.h_addrtype)
else:
if s.h_addrtype == posix.AF_INET:
result.addrtype = AF_INET
elif s.h_addrtype == posix.AF_INET6:
result.addrtype = AF_INET6
else:
raiseOSError(osLastError(), "unknown h_addrtype")
if result.addrtype == AF_INET:
result.addrList = @[]
var i = 0
while not isNil(s.h_addr_list[i]):
var inaddrPtr = cast[ptr InAddr](s.h_addr_list[i])
result.addrList.add($inet_ntoa(inaddrPtr[]))
inc(i)
else:
result.addrList = cstringArrayToSeq(s.h_addr_list)
result.length = int(s.h_length)
proc getHostname*(): string {.tags: [ReadIOEffect].} =
## Returns the local hostname (not the FQDN)
# https://tools.ietf.org/html/rfc1035#section-2.3.1
# https://tools.ietf.org/html/rfc2181#section-11
const size = 64
result = newString(size)
when useWinVersion:
let success = winlean.gethostname(result, size)
else:
# Posix
let success = posix.gethostname(result, size)
if success != 0.cint:
raiseOSError(osLastError())
let x = len(cstring(result))
result.setLen(x)
proc getSockDomain*(socket: SocketHandle): Domain =
## returns the socket's domain (AF_INET or AF_INET6).
var name: Sockaddr_in6
var namelen = sizeof(name).SockLen
if getsockname(socket, cast[ptr SockAddr](addr(name)),
addr(namelen)) == -1'i32:
raiseOSError(osLastError())
try:
result = toKnownDomain(name.sin6_family.cint).get()
except UnpackError:
raise newException(IOError, "Unknown socket family in getSockDomain")
proc getAddrString*(sockAddr: ptr SockAddr): string =
## return the string representation of address within sockAddr
if sockAddr.sa_family.cint == nativeAfInet:
result = $inet_ntoa(cast[ptr Sockaddr_in](sockAddr).sin_addr)
elif sockAddr.sa_family.cint == nativeAfInet6:
let addrLen = when not useWinVersion: posix.INET6_ADDRSTRLEN
else: 46 # it's actually 46 in both cases
result = newString(addrLen)
let addr6 = addr cast[ptr Sockaddr_in6](sockAddr).sin6_addr
when not useWinVersion:
if posix.inet_ntop(posix.AF_INET6, addr6, addr result[0],
result.len.int32) == nil:
raiseOSError(osLastError())
if posix.IN6_IS_ADDR_V4MAPPED(addr6) != 0:
result = result.substr("::ffff:".len)
else:
if winlean.inet_ntop(winlean.AF_INET6, addr6, addr result[0],
result.len.int32) == nil:
raiseOSError(osLastError())
setLen(result, len(cstring(result)))
else:
when defined(posix) and not defined(nimdoc):
if sockAddr.sa_family.cint == nativeAfUnix:
return "unix"
raise newException(IOError, "Unknown socket family in getAddrString")
when defined(posix) and not defined(nimdoc):
proc makeUnixAddr*(path: string): Sockaddr_un =
result.sun_family = AF_UNIX.TSa_Family
if path.len >= Sockaddr_un_path_length:
raise newException(ValueError, "socket path too long")
copyMem(addr result.sun_path, path.cstring, path.len + 1)
proc getSockName*(socket: SocketHandle): Port =
## returns the socket's associated port number.
var name: Sockaddr_in
when useWinVersion:
name.sin_family = uint16(ord(AF_INET))
else:
name.sin_family = TSa_Family(posix.AF_INET)
#name.sin_port = htons(cint16(port))
#name.sin_addr.s_addr = htonl(INADDR_ANY)
var namelen = sizeof(name).SockLen
if getsockname(socket, cast[ptr SockAddr](addr(name)),
addr(namelen)) == -1'i32:
raiseOSError(osLastError())
result = Port(nativesockets.ntohs(name.sin_port))
proc getLocalAddr*(socket: SocketHandle, domain: Domain): (string, Port) =
## returns the socket's local address and port number.
##
## Similar to POSIX's `getsockname`:idx:.
case domain
of AF_INET:
var name: Sockaddr_in
when useWinVersion:
name.sin_family = uint16(ord(AF_INET))
else:
name.sin_family = TSa_Family(posix.AF_INET)
var namelen = sizeof(name).SockLen
if getsockname(socket, cast[ptr SockAddr](addr(name)),
addr(namelen)) == -1'i32:
raiseOSError(osLastError())
result = ($inet_ntoa(name.sin_addr),
Port(nativesockets.ntohs(name.sin_port)))
of AF_INET6:
var name: Sockaddr_in6
when useWinVersion:
name.sin6_family = uint16(ord(AF_INET6))
else:
name.sin6_family = TSa_Family(posix.AF_INET6)
var namelen = sizeof(name).SockLen
if getsockname(socket, cast[ptr SockAddr](addr(name)),
addr(namelen)) == -1'i32:
raiseOSError(osLastError())
# Cannot use INET6_ADDRSTRLEN here, because it's a C define.
result[0] = newString(64)
if inet_ntop(name.sin6_family.cint,
addr name.sin6_addr, addr result[0][0], (result[0].len+1).int32).isNil:
raiseOSError(osLastError())
setLen(result[0], result[0].cstring.len)
result[1] = Port(nativesockets.ntohs(name.sin6_port))
else:
raiseOSError(OSErrorCode(-1), "invalid socket family in getLocalAddr")
proc getPeerAddr*(socket: SocketHandle, domain: Domain): (string, Port) =
## returns the socket's peer address and port number.
##
## Similar to POSIX's `getpeername`:idx:
case domain
of AF_INET:
var name: Sockaddr_in
when useWinVersion:
name.sin_family = uint16(ord(AF_INET))
else:
name.sin_family = TSa_Family(posix.AF_INET)
var namelen = sizeof(name).SockLen
if getpeername(socket, cast[ptr SockAddr](addr(name)),
addr(namelen)) == -1'i32:
raiseOSError(osLastError())
result = ($inet_ntoa(name.sin_addr),
Port(nativesockets.ntohs(name.sin_port)))
of AF_INET6:
var name: Sockaddr_in6
when useWinVersion:
name.sin6_family = uint16(ord(AF_INET6))
else:
name.sin6_family = TSa_Family(posix.AF_INET6)
var namelen = sizeof(name).SockLen
if getpeername(socket, cast[ptr SockAddr](addr(name)),
addr(namelen)) == -1'i32:
raiseOSError(osLastError())
# Cannot use INET6_ADDRSTRLEN here, because it's a C define.
result[0] = newString(64)
if inet_ntop(name.sin6_family.cint,
addr name.sin6_addr, addr result[0][0], (result[0].len+1).int32).isNil:
raiseOSError(osLastError())
setLen(result[0], result[0].cstring.len)
result[1] = Port(nativesockets.ntohs(name.sin6_port))
else:
raiseOSError(OSErrorCode(-1), "invalid socket family in getLocalAddr")
proc getSockOptInt*(socket: SocketHandle, level, optname: int): int {.
tags: [ReadIOEffect].} =
## getsockopt for integer options.
var res: cint
var size = sizeof(res).SockLen
if getsockopt(socket, cint(level), cint(optname),
addr(res), addr(size)) < 0'i32:
raiseOSError(osLastError())
result = int(res)
proc setSockOptInt*(socket: SocketHandle, level, optname, optval: int) {.
tags: [WriteIOEffect].} =
## setsockopt for integer options.
var value = cint(optval)
if setsockopt(socket, cint(level), cint(optname), addr(value),
sizeof(value).SockLen) < 0'i32:
raiseOSError(osLastError())
proc setBlocking*(s: SocketHandle, blocking: bool) =
## Sets blocking mode on socket.
##
## Raises OSError on error.
when useWinVersion:
var mode = clong(ord(not blocking)) # 1 for non-blocking, 0 for blocking
if ioctlsocket(s, FIONBIO, addr(mode)) == -1:
raiseOSError(osLastError())
else: # BSD sockets
var x: int = fcntl(s, F_GETFL, 0)
if x == -1:
raiseOSError(osLastError())
else:
var mode = if blocking: x and not O_NONBLOCK else: x or O_NONBLOCK
if fcntl(s, F_SETFL, mode) == -1:
raiseOSError(osLastError())
proc timeValFromMilliseconds(timeout = 500): Timeval =
if timeout != -1:
var seconds = timeout div 1000
when useWinVersion:
result.tv_sec = seconds.int32
result.tv_usec = ((timeout - seconds * 1000) * 1000).int32
else:
result.tv_sec = seconds.Time
result.tv_usec = ((timeout - seconds * 1000) * 1000).Suseconds
proc createFdSet(fd: var TFdSet, s: seq[SocketHandle], m: var int) =
FD_ZERO(fd)
for i in items(s):
m = max(m, int(i))
FD_SET(i, fd)
proc pruneSocketSet(s: var seq[SocketHandle], fd: var TFdSet) =
var i = 0
var L = s.len
while i < L:
if FD_ISSET(s[i], fd) == 0'i32:
s[i] = s[L-1]
dec(L)
else:
inc(i)
setLen(s, L)
proc selectRead*(readfds: var seq[SocketHandle], timeout = 500): int =
## When a socket in ``readfds`` is ready to be read from then a non-zero
## value will be returned specifying the count of the sockets which can be
## read from. The sockets which can be read from will also be removed
## from ``readfds``.
##
## ``timeout`` is specified in milliseconds and ``-1`` can be specified for
## an unlimited time.
var tv {.noinit.}: Timeval = timeValFromMilliseconds(timeout)
var rd: TFdSet
var m = 0
createFdSet((rd), readfds, m)
if timeout != -1:
result = int(select(cint(m+1), addr(rd), nil, nil, addr(tv)))
else:
result = int(select(cint(m+1), addr(rd), nil, nil, nil))
pruneSocketSet(readfds, (rd))
proc selectWrite*(writefds: var seq[SocketHandle],
timeout = 500): int {.tags: [ReadIOEffect].} =
## When a socket in ``writefds`` is ready to be written to then a non-zero
## value will be returned specifying the count of the sockets which can be
## written to. The sockets which can be written to will also be removed
## from ``writefds``.
##
## ``timeout`` is specified in milliseconds and ``-1`` can be specified for
## an unlimited time.
var tv {.noinit.}: Timeval = timeValFromMilliseconds(timeout)
var wr: TFdSet
var m = 0
createFdSet((wr), writefds, m)
if timeout != -1:
result = int(select(cint(m+1), nil, addr(wr), nil, addr(tv)))
else:
result = int(select(cint(m+1), nil, addr(wr), nil, nil))
pruneSocketSet(writefds, (wr))
proc accept*(fd: SocketHandle, inheritable = defined(nimInheritHandles)): (SocketHandle, string) =
## Accepts a new client connection.
##
## `inheritable` decides if the resulting SocketHandle can be inherited by
## child processes.
##
## Returns (osInvalidSocket, "") if an error occurred.
var sockAddress: Sockaddr_in
var addrLen = sizeof(sockAddress).SockLen
var sock =
when (defined(linux) or defined(bsd)) and not defined(nimdoc):
accept4(fd, cast[ptr SockAddr](addr(sockAddress)), addr(addrLen),
if inheritable: 0 else: SOCK_CLOEXEC)
else:
accept(fd, cast[ptr SockAddr](addr(sockAddress)), addr(addrLen))
when declared(setInheritable) and not (defined(linux) or defined(bsd)):
if not setInheritable(sock, inheritable):
close sock
sock = osInvalidSocket
if sock == osInvalidSocket:
return (osInvalidSocket, "")
else:
return (sock, $inet_ntoa(sockAddress.sin_addr))
when defined(Windows):
var wsa: WSAData
if wsaStartup(0x0101'i16, addr wsa) != 0: raiseOSError(osLastError())