Implement dial, support IPv6 in httpclient (#5763)

* Implement dial, support IPv6 in httpclient

Added ``dial`` procedure to networking modules: ``net``, ``asyncdispatch``,
``asyncnet``. It merges socket creation, address resolution, and connection
into single step. When using ``dial``, you don't have to worry about
IPv4 vs IPv6 problem.

Fixed addrInfo loop in connect to behave properly.
Previously it would stop on first non-immediate failure, instead of
continuing and trying the remaining addresses.

Fixed newAsyncNativeSocket to raise proper error if socket creation
fails.

Fixes: #3811

* Check domain during connect() only on non-Windows

This is how it was in the previous implementation of connect().

* Call 'osLastError' before 'close' in net.dial

* Record osLastError before freeAddrInfo in net.dial

* Add missing docs for 'dial' proc

* Optimize dial to create one FD per domain, add tests

And make async IPv6 servers work on Windows.

* Add IPv6 test to uri module

* Fix getAddrString error handling
This commit is contained in:
Ruslan Mustakov 2017-05-02 14:25:50 +07:00 • committed by Andreas Rumpf
commit ecf278c467
13 changed files with 559 additions and 334 deletions

View file

@ -12,7 +12,7 @@
# TODO: Clean up the exports a bit and everything else in general.
import os
import os, options
when hostOS == "solaris":
{.passl: "-lsocket -lnsl".}
@ -52,9 +52,11 @@ type
Domain* = enum ## domain, which specifies the protocol family of the
## created socket. Other domains than those that are listed
## here are unsupported.
AF_UNIX, ## for local socket (using a file). Unsupported on Windows.
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 = 23 ## for network protocol IPv6.
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
@ -125,10 +127,19 @@ proc toInt*(p: Protocol): cint
when not useWinVersion:
proc toInt(domain: Domain): cshort =
case domain
of AF_UNSPEC: result = posix.AF_UNSPEC.cshort
of AF_UNIX: result = posix.AF_UNIX.cshort
of AF_INET: result = posix.AF_INET.cshort
of AF_INET6: result = posix.AF_INET6.cshort
else: discard
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
@ -136,7 +147,6 @@ when not useWinVersion:
of SOCK_DGRAM: result = posix.SOCK_DGRAM
of SOCK_SEQPACKET: result = posix.SOCK_SEQPACKET
of SOCK_RAW: result = posix.SOCK_RAW
else: discard
proc toInt(p: Protocol): cint =
case p
@ -146,18 +156,33 @@ when not useWinVersion:
of IPPROTO_IPV6: result = posix.IPPROTO_IPV6
of IPPROTO_RAW: result = posix.IPPROTO_RAW
of IPPROTO_ICMP: result = posix.IPPROTO_ICMP
else: discard
else:
proc toInt(domain: Domain): cshort =
result = toU16(ord(domain))
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:
SOCK_RAW
proc newNativeSocket*(domain: Domain = AF_INET,
sockType: SockType = SOCK_STREAM,
@ -392,14 +417,14 @@ proc getHostname*(): string {.tags: [ReadIOEffect].} =
proc getSockDomain*(socket: SocketHandle): Domain =
## returns the socket's domain (AF_INET or AF_INET6).
var name: SockAddr
var name: Sockaddr_in6
var namelen = sizeof(name).SockLen
if getsockname(socket, cast[ptr SockAddr](addr(name)),
addr(namelen)) == -1'i32:
raiseOSError(osLastError())
if name.sa_family == nativeAfInet:
if name.sin6_family == nativeAfInet:
result = AF_INET
elif name.sa_family == nativeAfInet6:
elif name.sin6_family == nativeAfInet6:
result = AF_INET6
else:
raiseOSError(osLastError(), "unknown socket family in getSockFamily")
@ -410,17 +435,23 @@ proc getAddrString*(sockAddr: ptr SockAddr): string =
if sockAddr.sa_family == nativeAfInet:
result = $inet_ntoa(cast[ptr Sockaddr_in](sockAddr).sin_addr)
elif sockAddr.sa_family == 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:
# TODO: Windows
result = newString(posix.INET6_ADDRSTRLEN)
let addr6 = addr cast[ptr Sockaddr_in6](sockAddr).sin6_addr
discard posix.inet_ntop(posix.AF_INET6, addr6, result.cstring,
result.len.int32)
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:
raiseOSError(osLastError(), "unknown socket family in getAddrString")
raise newException(IOError, "Unknown socket family in getAddrString")
proc getSockName*(socket: SocketHandle): Port =
## returns the socket's associated port number.