mirror of
https://github.com/nim-lang/Nim.git
synced 2026-01-02 03:02:31 +00:00
* io: make file descriptors non-inheritable by default This prevents file descriptors/handles leakage to child processes that might cause issues like running out of file descriptors, or potential security issues like leaking a file descriptor to a restricted file. While this breaks backward compatibility, I'm rather certain that not many programs (if any) actually make use of this implementation detail. A new API `setInheritable` is provided for the few that actually want to use this functionality. * io: disable inheritance at file creation time for supported platforms Some platforms provide extension to fopen-family of functions to allow for disabling descriptor inheritance atomically during File creation. This guards against possible leaks when a child process is spawned before we managed to disable the file descriptor inheritance (ie. in a multi-threaded program). * net, nativesockets: make sockets non inheritable by default With this commit, sockets will no longer leak to child processes when you don't want it to. Should solves a lot of "address in use" that might occur when your server has just restarted. All APIs that create sockets in these modules now expose a `inheritable` flag that allow users to toggle inheritance for the resulting sockets. An implementation of `setInheritance()` is also provided for SocketHandle. While atomically disabling inheritance at creation time is supported on Windows, it's only implemented by native winsock2, which is too much for now. This support can be implemented in a future patch. * posix: add F_DUPFD_CLOEXEC This command duplicates file descriptor with close-on-exec flag set. Defined in POSIX.1-2008. * ioselectors_kqueue: don't leak file descriptors File descriptors internally used by ioselectors on BSD/OSX are now shielded from leakage. * posix: add O_CLOEXEC This flag allows file descriptors to be open() with close-on-exec flag set atomically. This flag is specified in POSIX.1-2008 * tfdleak: test for selectors leakage Also simplified the test by using handle-type agnostic APIs to test for validity. * ioselectors_epoll: mark all fd created close-on-exec File descriptors from ioselectors should no longer leaks on Linux. * tfdleak: don't check for selector leakage on Windows The getFd proc for ioselectors_select returns a hardcoded -1 * io: add NoInheritFlag at compile time * io: add support for ioctl-based close-on-exec This allows for the flag to be set/unset in one syscall. While the performance gains might be negliable, we have one less failure point to deal with. * tfdleak: add a test for setInheritable * stdlib: add nimInheritHandles to restore old behaviors * memfiles: make file handle not inheritable by default for posix * io: setInheritable now operates on OS file handle On Windows, the native handle is the only thing that's inheritable, thus we can assume that users of this function will already have the handle available to them. This also allows users to pass down file descriptors from memfiles on Windows with ease, should that be desired. With this, nativesockets.setInheritable can be made much simpler. * changelog: clarify * nativesockets: document setInheritable return value * posix_utils: atomically disable fd inheritance for mkstemp
707 lines
26 KiB
Nim
707 lines
26 KiB
Nim
#
|
|
#
|
|
# 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())
|