Fixes socket problems on Windows and normalises some names.

Ref #2976. Ref #2003. See news.txt for details.
This commit is contained in:
Dominik Picheta 2015-06-29 20:11:21 +01:00
commit 615defb1a9
5 changed files with 78 additions and 55 deletions

View file

@ -483,7 +483,7 @@ when defined(windows) or defined(nimdoc):
RemoteSockaddr, RemoteSockaddrLength) RemoteSockaddr, RemoteSockaddrLength)
proc connect*(socket: AsyncFD, address: string, port: Port, proc connect*(socket: AsyncFD, address: string, port: Port,
af = rawsockets.AF_INET): Future[void] = domain = rawsockets.AF_INET): Future[void] =
## Connects ``socket`` to server at ``address:port``. ## Connects ``socket`` to server at ``address:port``.
## ##
## Returns a ``Future`` which will complete when the connection succeeds ## Returns a ``Future`` which will complete when the connection succeeds
@ -492,14 +492,14 @@ when defined(windows) or defined(nimdoc):
var retFuture = newFuture[void]("connect") var retFuture = newFuture[void]("connect")
# Apparently ``ConnectEx`` expects the socket to be initially bound: # Apparently ``ConnectEx`` expects the socket to be initially bound:
var saddr: Sockaddr_in var saddr: Sockaddr_in
saddr.sin_family = int16(toInt(af)) saddr.sin_family = int16(toInt(domain))
saddr.sin_port = 0 saddr.sin_port = 0
saddr.sin_addr.s_addr = INADDR_ANY saddr.sin_addr.s_addr = INADDR_ANY
if bindAddr(socket.SocketHandle, cast[ptr SockAddr](addr(saddr)), if bindAddr(socket.SocketHandle, cast[ptr SockAddr](addr(saddr)),
sizeof(saddr).SockLen) < 0'i32: sizeof(saddr).SockLen) < 0'i32:
raiseOSError(osLastError()) raiseOSError(osLastError())
var aiList = getAddrInfo(address, port, af) var aiList = getAddrInfo(address, port, domain)
var success = false var success = false
var lastError: OSErrorCode var lastError: OSErrorCode
var it = aiList var it = aiList
@ -855,17 +855,17 @@ when defined(windows) or defined(nimdoc):
return retFuture return retFuture
proc newAsyncRawSocket*(domain, typ, protocol: cint): AsyncFD = proc newAsyncRawSocket*(domain, sockType, protocol: cint): AsyncFD =
## Creates a new socket and registers it with the dispatcher implicitly. ## Creates a new socket and registers it with the dispatcher implicitly.
result = newRawSocket(domain, typ, protocol).AsyncFD result = newRawSocket(domain, sockType, protocol).AsyncFD
result.SocketHandle.setBlocking(false) result.SocketHandle.setBlocking(false)
register(result) register(result)
proc newAsyncRawSocket*(domain: Domain = rawsockets.AF_INET, proc newAsyncRawSocket*(domain: Domain = rawsockets.AF_INET,
typ: SockType = SOCK_STREAM, sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): AsyncFD = protocol: Protocol = IPPROTO_TCP): AsyncFD =
## Creates a new socket and registers it with the dispatcher implicitly. ## Creates a new socket and registers it with the dispatcher implicitly.
result = newRawSocket(domain, typ, protocol).AsyncFD result = newRawSocket(domain, sockType, protocol).AsyncFD
result.SocketHandle.setBlocking(false) result.SocketHandle.setBlocking(false)
register(result) register(result)
@ -928,17 +928,18 @@ else:
var data = PData(fd: fd, readCBs: @[], writeCBs: @[]) var data = PData(fd: fd, readCBs: @[], writeCBs: @[])
p.selector.register(fd.SocketHandle, {}, data.RootRef) p.selector.register(fd.SocketHandle, {}, data.RootRef)
proc newAsyncRawSocket*(domain: cint, typ: cint, protocol: cint): AsyncFD = proc newAsyncRawSocket*(domain: cint, sockType: cint,
result = newRawSocket(domain, typ, protocol).AsyncFD protocol: cint): AsyncFD =
result = newRawSocket(domain, sockType, protocol).AsyncFD
result.SocketHandle.setBlocking(false) result.SocketHandle.setBlocking(false)
when defined(macosx): when defined(macosx):
result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1) result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1)
register(result) register(result)
proc newAsyncRawSocket*(domain: Domain = AF_INET, proc newAsyncRawSocket*(domain: Domain = AF_INET,
typ: SockType = SOCK_STREAM, sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): AsyncFD = protocol: Protocol = IPPROTO_TCP): AsyncFD =
result = newRawSocket(domain, typ, protocol).AsyncFD result = newRawSocket(domain, sockType, protocol).AsyncFD
result.SocketHandle.setBlocking(false) result.SocketHandle.setBlocking(false)
when defined(macosx): when defined(macosx):
result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1) result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1)
@ -1009,7 +1010,7 @@ else:
processTimers(p) processTimers(p)
proc connect*(socket: AsyncFD, address: string, port: Port, proc connect*(socket: AsyncFD, address: string, port: Port,
af_unused = AF_INET): Future[void] = domain = AF_INET): Future[void] =
var retFuture = newFuture[void]("connect") var retFuture = newFuture[void]("connect")
proc cb(fd: AsyncFD): bool = proc cb(fd: AsyncFD): bool =
@ -1017,8 +1018,8 @@ else:
retFuture.complete() retFuture.complete()
return true return true
var sockDomain = getSockDomain(socket.SocketHandle) assert getSockDomain(socket.SocketHandle) == domain
var aiList = getAddrInfo(address, port, sockDomain) var aiList = getAddrInfo(address, port, domain)
var success = false var success = false
var lastError: OSErrorCode var lastError: OSErrorCode
var it = aiList var it = aiList
@ -1496,8 +1497,8 @@ macro async*(prc: stmt): stmt {.immediate.} =
result[6] = outerProcBody result[6] = outerProcBody
#echo(treeRepr(result)) #echo(treeRepr(result))
#if prc[0].getName == "test": if prc[0].getName == "getAsync":
# echo(toStrLit(result)) echo(toStrLit(result))
proc recvLine*(socket: AsyncFD): Future[string] {.async.} = proc recvLine*(socket: AsyncFD): Future[string] {.async.} =
## Reads a line of data from ``socket``. Returned future will complete once ## Reads a line of data from ``socket``. Returned future will complete once

View file

@ -85,35 +85,44 @@ type
bioIn: BIO bioIn: BIO
bioOut: BIO bioOut: BIO
of false: nil of false: nil
domain: Domain
sockType: SockType
protocol: Protocol
AsyncSocket* = ref AsyncSocketDesc AsyncSocket* = ref AsyncSocketDesc
{.deprecated: [PAsyncSocket: AsyncSocket].} {.deprecated: [PAsyncSocket: AsyncSocket].}
# TODO: Save AF, domain etc info and reuse it in procs which need it like connect. proc newAsyncSocket*(fd: AsyncFD, domain: Domain = AF_INET,
sockType: SockType = SOCK_STREAM,
proc newAsyncSocket*(fd: AsyncFD, buffered = true): AsyncSocket = protocol: Protocol = IPPROTO_TCP, buffered = true): AsyncSocket =
## Creates a new ``AsyncSocket`` based on the supplied params. ## Creates a new ``AsyncSocket`` based on the supplied params.
assert fd != osInvalidSocket.AsyncFD assert fd != osInvalidSocket.AsyncFD
new(result) new(result)
result.fd = fd.SocketHandle result.fd = fd.SocketHandle
result.isBuffered = buffered result.isBuffered = buffered
result.domain = domain
result.sockType = sockType
result.protocol = protocol
if buffered: if buffered:
result.currPos = 0 result.currPos = 0
proc newAsyncSocket*(domain: Domain = AF_INET6, typ: SockType = SOCK_STREAM, proc newAsyncSocket*(domain: Domain = AF_INET, sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP, buffered = true): AsyncSocket = protocol: Protocol = IPPROTO_TCP, buffered = true): AsyncSocket =
## Creates a new asynchronous socket. ## Creates a new asynchronous socket.
## ##
## This procedure will also create a brand new file descriptor for ## This procedure will also create a brand new file descriptor for
## this socket. ## this socket.
result = newAsyncSocket(newAsyncRawSocket(domain, typ, protocol), buffered) result = newAsyncSocket(newAsyncRawSocket(domain, sockType, protocol), domain,
sockType, protocol, buffered)
proc newAsyncSocket*(domain, typ, protocol: cint, buffered = true): AsyncSocket = proc newAsyncSocket*(domain, sockType, protocol: cint,
buffered = true): AsyncSocket =
## Creates a new asynchronous socket. ## Creates a new asynchronous socket.
## ##
## This procedure will also create a brand new file descriptor for ## This procedure will also create a brand new file descriptor for
## this socket. ## this socket.
result = newAsyncSocket(newAsyncRawSocket(domain, typ, protocol), buffered) result = newAsyncSocket(newAsyncRawSocket(domain, sockType, protocol),
Domain(domain), SockType(sockType), Protocol(protocol), buffered)
when defined(ssl): when defined(ssl):
proc getSslError(handle: SslPtr, err: cint): cint = proc getSslError(handle: SslPtr, err: cint): cint =
@ -169,13 +178,12 @@ when defined(ssl):
let err = getSslError(socket.sslHandle, opResult.cint) let err = getSslError(socket.sslHandle, opResult.cint)
yield appeaseSsl(socket, flags, err.cint) yield appeaseSsl(socket, flags, err.cint)
proc connect*(socket: AsyncSocket, address: string, port: Port, proc connect*(socket: AsyncSocket, address: string, port: Port) {.async.} =
af = AF_INET) {.async.} =
## Connects ``socket`` to server at ``address:port``. ## Connects ``socket`` to server at ``address:port``.
## ##
## Returns a ``Future`` which will complete when the connection succeeds ## Returns a ``Future`` which will complete when the connection succeeds
## or an error occurs. ## or an error occurs.
await connect(socket.fd.AsyncFD, address, port, af) await connect(socket.fd.AsyncFD, address, port, socket.domain)
if socket.isSsl: if socket.isSsl:
when defined(ssl): when defined(ssl):
let flags = {SocketFlag.SafeDisconn} let flags = {SocketFlag.SafeDisconn}
@ -287,7 +295,8 @@ proc acceptAddr*(socket: AsyncSocket, flags = {SocketFlag.SafeDisconn}):
retFuture.fail(future.readError) retFuture.fail(future.readError)
else: else:
let resultTup = (future.read.address, let resultTup = (future.read.address,
newAsyncSocket(future.read.client, socket.isBuffered)) newAsyncSocket(future.read.client, socket.domain,
socket.sockType, socket.protocol, socket.isBuffered))
retFuture.complete(resultTup) retFuture.complete(resultTup)
return retFuture return retFuture
@ -423,17 +432,15 @@ proc bindAddr*(socket: AsyncSocket, port = Port(0), address = "") {.
## Binds ``address``:``port`` to the socket. ## Binds ``address``:``port`` to the socket.
## ##
## If ``address`` is "" then ADDR_ANY will be bound. ## If ``address`` is "" then ADDR_ANY will be bound.
var sockDomain = getSockDomain(socket.fd)
var realaddr = address var realaddr = address
if realaddr == "": if realaddr == "":
case sockDomain case socket.domain
of AF_INET6: realaddr = "::" of AF_INET6: realaddr = "::"
of AF_INET: realaddr = "0.0.0.0" of AF_INET: realaddr = "0.0.0.0"
else: else:
raiseOSError("Unknown socket address family and no address specified to bindAddr") raiseOSError("Unknown socket address family and no address specified to bindAddr")
var aiList = getAddrInfo(realaddr, port, sockDomain) var aiList = getAddrInfo(realaddr, port, socket.domain)
if bindAddr(socket.fd, aiList.ai_addr, aiList.ai_addrlen.Socklen) < 0'i32: if bindAddr(socket.fd, aiList.ai_addr, aiList.ai_addrlen.Socklen) < 0'i32:
dealloc(aiList) dealloc(aiList)
raiseOSError(osLastError()) raiseOSError(osLastError())

View file

@ -64,6 +64,9 @@ type
sslPeekChar: char sslPeekChar: char
of false: nil of false: nil
lastError: OSErrorCode ## stores the last error on this socket lastError: OSErrorCode ## stores the last error on this socket
domain: Domain
sockType: SockType
protocol: Protocol
Socket* = ref SocketImpl Socket* = ref SocketImpl
@ -124,33 +127,39 @@ proc toOSFlags*(socketFlags: set[SocketFlag]): cint =
result = result or MSG_PEEK result = result or MSG_PEEK
of SocketFlag.SafeDisconn: continue of SocketFlag.SafeDisconn: continue
proc newSocket*(fd: SocketHandle, buffered = true): Socket = proc newSocket*(fd: SocketHandle, domain: Domain = AF_INET,
sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP, buffered = true): Socket =
## Creates a new socket as specified by the params. ## Creates a new socket as specified by the params.
assert fd != osInvalidSocket assert fd != osInvalidSocket
new(result) new(result)
result.fd = fd result.fd = fd
result.isBuffered = buffered result.isBuffered = buffered
result.domain = domain
result.sockType = sockType
result.protocol = protocol
if buffered: if buffered:
result.currPos = 0 result.currPos = 0
proc newSocket*(domain, typ, protocol: cint, buffered = true): Socket = proc newSocket*(domain, sockType, protocol: cint, buffered = true): Socket =
## Creates a new socket. ## Creates a new socket.
## ##
## If an error occurs EOS will be raised. ## If an error occurs EOS will be raised.
let fd = newRawSocket(domain, typ, protocol) let fd = newRawSocket(domain, sockType, protocol)
if fd == osInvalidSocket: if fd == osInvalidSocket:
raiseOSError(osLastError()) raiseOSError(osLastError())
result = newSocket(fd, buffered) result = newSocket(fd, domain.Domain, sockType.SockType, protocol.Protocol,
buffered)
proc newSocket*(domain: Domain = AF_INET, typ: SockType = SOCK_STREAM, proc newSocket*(domain: Domain = AF_INET, sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP, buffered = true): Socket = protocol: Protocol = IPPROTO_TCP, buffered = true): Socket =
## Creates a new socket. ## Creates a new socket.
## ##
## If an error occurs EOS will be raised. ## If an error occurs EOS will be raised.
let fd = newRawSocket(domain, typ, protocol) let fd = newRawSocket(domain, sockType, protocol)
if fd == osInvalidSocket: if fd == osInvalidSocket:
raiseOSError(osLastError()) raiseOSError(osLastError())
result = newSocket(fd, buffered) result = newSocket(fd, domain, sockType, protocol, buffered)
when defined(ssl): when defined(ssl):
CRYPTO_malloc_init() CRYPTO_malloc_init()
@ -371,7 +380,7 @@ proc bindAddr*(socket: Socket, port = Port(0), address = "") {.
sizeof(name).SockLen) < 0'i32: sizeof(name).SockLen) < 0'i32:
raiseOSError(osLastError()) raiseOSError(osLastError())
else: else:
var aiList = getAddrInfo(address, port, AF_INET) var aiList = getAddrInfo(address, port, socket.domain)
if bindAddr(socket.fd, aiList.ai_addr, aiList.ai_addrlen.SockLen) < 0'i32: if bindAddr(socket.fd, aiList.ai_addr, aiList.ai_addrlen.SockLen) < 0'i32:
dealloc(aiList) dealloc(aiList)
raiseOSError(osLastError()) raiseOSError(osLastError())
@ -532,15 +541,15 @@ proc setSockOpt*(socket: Socket, opt: SOBool, value: bool, level = SOL_SOCKET) {
var valuei = cint(if value: 1 else: 0) var valuei = cint(if value: 1 else: 0)
setSockOptInt(socket.fd, cint(level), toCInt(opt), valuei) setSockOptInt(socket.fd, cint(level), toCInt(opt), valuei)
proc connect*(socket: Socket, address: string, port = Port(0), proc connect*(socket: Socket, address: string,
af: Domain = AF_INET) {.tags: [ReadIOEffect].} = port = Port(0)) {.tags: [ReadIOEffect].} =
## Connects socket to ``address``:``port``. ``Address`` can be an IP address or a ## Connects socket to ``address``:``port``. ``Address`` can be an IP address or a
## host name. If ``address`` is a host name, this function will try each IP ## host name. If ``address`` is a host name, this function will try each IP
## of that host name. ``htons`` is already performed on ``port`` so you must ## of that host name. ``htons`` is already performed on ``port`` so you must
## not do it. ## not do it.
## ##
## If ``socket`` is an SSL socket a handshake will be automatically performed. ## If ``socket`` is an SSL socket a handshake will be automatically performed.
var aiList = getAddrInfo(address, port, af) var aiList = getAddrInfo(address, port, socket.domain)
# try all possibilities: # try all possibilities:
var success = false var success = false
var lastError: OSErrorCode var lastError: OSErrorCode
@ -992,15 +1001,15 @@ proc connectAsync(socket: Socket, name: string, port = Port(0),
dealloc(aiList) dealloc(aiList)
if not success: raiseOSError(lastError) if not success: raiseOSError(lastError)
proc connect*(socket: Socket, address: string, port = Port(0), timeout: int, proc connect*(socket: Socket, address: string, port = Port(0),
af: Domain = AF_INET) {.tags: [ReadIOEffect, WriteIOEffect].} = timeout: int) {.tags: [ReadIOEffect, WriteIOEffect].} =
## Connects to server as specified by ``address`` on port specified by ``port``. ## Connects to server as specified by ``address`` on port specified by ``port``.
## ##
## The ``timeout`` paremeter specifies the time in milliseconds to allow for ## The ``timeout`` paremeter specifies the time in milliseconds to allow for
## the connection to the server to be made. ## the connection to the server to be made.
socket.fd.setBlocking(false) socket.fd.setBlocking(false)
socket.connectAsync(address, port, af) socket.connectAsync(address, port, socket.domain)
var s = @[socket.fd] var s = @[socket.fd]
if selectWrite(s, timeout) != 1: if selectWrite(s, timeout) != 1:
raise newException(TimeoutError, "Call to 'connect' timed out.") raise newException(TimeoutError, "Call to 'connect' timed out.")

View file

@ -157,17 +157,17 @@ else:
result = cint(ord(p)) result = cint(ord(p))
proc newRawSocket*(domain: Domain = AF_INET, typ: SockType = SOCK_STREAM, proc newRawSocket*(domain: Domain = AF_INET, sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): SocketHandle = protocol: Protocol = IPPROTO_TCP): SocketHandle =
## Creates a new socket; returns `InvalidSocket` if an error occurs. ## Creates a new socket; returns `InvalidSocket` if an error occurs.
socket(toInt(domain), toInt(typ), toInt(protocol)) socket(toInt(domain), toInt(sockType), toInt(protocol))
proc newRawSocket*(domain: cint, typ: cint, protocol: cint): SocketHandle = proc newRawSocket*(domain: cint, sockType: cint, protocol: cint): SocketHandle =
## Creates a new socket; returns `InvalidSocket` if an error occurs. ## Creates a new socket; returns `InvalidSocket` if an error occurs.
## ##
## Use this overload if one of the enums specified above does ## Use this overload if one of the enums specified above does
## not contain what you need. ## not contain what you need.
socket(domain, typ, protocol) socket(domain, sockType, protocol)
proc close*(socket: SocketHandle) = proc close*(socket: SocketHandle) =
## closes a socket. ## closes a socket.
@ -190,16 +190,17 @@ proc listen*(socket: SocketHandle, backlog = SOMAXCONN): cint {.tags: [ReadIOEff
else: else:
result = posix.listen(socket, cint(backlog)) result = posix.listen(socket, cint(backlog))
proc getAddrInfo*(address: string, port: Port, af: Domain = AF_INET, typ: SockType = SOCK_STREAM, proc getAddrInfo*(address: string, port: Port, domain: Domain = AF_INET,
prot: Protocol = IPPROTO_TCP): ptr AddrInfo = sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): ptr AddrInfo =
## ##
## ##
## **Warning**: The resulting ``ptr TAddrInfo`` must be freed using ``dealloc``! ## **Warning**: The resulting ``ptr TAddrInfo`` must be freed using ``dealloc``!
var hints: AddrInfo var hints: AddrInfo
result = nil result = nil
hints.ai_family = toInt(af) hints.ai_family = toInt(domain)
hints.ai_socktype = toInt(typ) hints.ai_socktype = toInt(sockType)
hints.ai_protocol = toInt(prot) hints.ai_protocol = toInt(protocol)
hints.ai_flags = AI_V4MAPPED hints.ai_flags = AI_V4MAPPED
var gaiResult = getaddrinfo(address, $port, addr(hints), result) var gaiResult = getaddrinfo(address, $port, addr(hints), result)
if gaiResult != 0'i32: if gaiResult != 0'i32:

View file

@ -28,7 +28,12 @@ News
``--gc:none``. ``--os:standalone`` without ``--gc:none`` is now a version ``--gc:none``. ``--os:standalone`` without ``--gc:none`` is now a version
that doesn't depend on any OS but includes the GC. However this version that doesn't depend on any OS but includes the GC. However this version
is currently untested! is currently untested!
- All procedures which construct a ``Socket``/``AsyncSocket`` now need to
specify the socket domain, type and protocol. The param name
``typ: SockType`` (in ``newSocket``/``newAsyncSocket`` procs) was also
renamed to ``sockType``. The param ``af`` in the ``connect`` procs was
removed. This affects ``asyncnet``, ``asyncdispatch``, ``net``, and
``rawsockets``.
Library additions Library additions
----------------- -----------------