From c15a8993ecc3c6dfdba430cca331534a1eef3ed8 Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Sat, 29 May 2021 09:50:47 +0200 Subject: Use sockets with new IP Address structs --- README.md | 5 +- source/tanya/async/event/epoll.d | 2 +- source/tanya/async/event/selector.d | 2 +- source/tanya/async/loop.d | 16 +- source/tanya/async/protocol.d | 2 +- source/tanya/async/transport.d | 2 +- source/tanya/async/watcher.d | 2 +- source/tanya/net/iface.d | 20 + source/tanya/net/ip.d | 37 +- source/tanya/net/package.d | 1 + source/tanya/net/socket.d | 1258 ++++++++++++++++++++++++++++++ source/tanya/network/package.d | 17 - source/tanya/network/socket.d | 1458 ----------------------------------- 13 files changed, 1318 insertions(+), 1504 deletions(-) create mode 100644 source/tanya/net/socket.d delete mode 100644 source/tanya/network/package.d delete mode 100644 source/tanya/network/socket.d diff --git a/README.md b/README.md index e58cd04..3e27532 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,6 @@ type information at compile-time, to transform from one type to another. It has also different algorithms for iterating, searching and modifying template arguments. * `net`: URL-Parsing, network programming. -* `network`: Socket implementation. `network` is currently under rework. -After finishing the new socket implementation will land in the `net` package and -`network` will be deprecated. * `os`: Platform-independent interfaces to operating system functionality. * `range`: Generic functions and templates for D ranges. * `test`: Test suite for unittest-blocks. @@ -167,7 +164,7 @@ parameter is used) | DMD | GCC | |:-------:|:---------:| -| 2.091.1 | gdc trunk | +| 2.096.0 | 10.3 | ## Further characteristics diff --git a/source/tanya/async/event/epoll.d b/source/tanya/async/event/epoll.d index 7d3854d..c81d6ff 100644 --- a/source/tanya/async/event/epoll.d +++ b/source/tanya/async/event/epoll.d @@ -31,7 +31,7 @@ import tanya.async.transport; import tanya.async.watcher; import tanya.container.array; import tanya.memory.allocator; -import tanya.network.socket; +import tanya.net.socket; extern (C) nothrow @nogc { diff --git a/source/tanya/async/event/selector.d b/source/tanya/async/event/selector.d index 897ad99..f954e8c 100644 --- a/source/tanya/async/event/selector.d +++ b/source/tanya/async/event/selector.d @@ -26,7 +26,7 @@ import tanya.async.watcher; import tanya.container.array; import tanya.container.buffer; import tanya.memory.allocator; -import tanya.network.socket; +import tanya.net.socket; /** * Transport for stream sockets. diff --git a/source/tanya/async/loop.d b/source/tanya/async/loop.d index 6653282..1d88575 100644 --- a/source/tanya/async/loop.d +++ b/source/tanya/async/loop.d @@ -34,8 +34,9 @@ * * void main() * { - * auto address = defaultAllocator.make!InternetAddress("127.0.0.1", cast(ushort) 8192); - * + * auto address = address4("127.0.0.1"); + * auto endpoint = Endpoint(address.get, cast(ushort) 8192); + * * version (Windows) * { * auto sock = defaultAllocator.make!OverlappedStreamSocket(AddressFamily.inet); @@ -46,19 +47,18 @@ * sock.blocking = false; * } * - * sock.bind(address); + * sock.bind(endpoint); * sock.listen(5); - * + * * auto io = defaultAllocator.make!ConnectionWatcher(sock); * io.setProtocol!EchoProtocol; - * + * * defaultLoop.start(io); * defaultLoop.run(); - * + * * sock.shutdown(); * defaultAllocator.dispose(io); * defaultAllocator.dispose(sock); - * defaultAllocator.dispose(address); * } * --- * @@ -78,7 +78,7 @@ import tanya.bitmanip; import tanya.container.buffer; import tanya.container.list; import tanya.memory.allocator; -import tanya.network.socket; +import tanya.net.socket; version (DisableBackends) { diff --git a/source/tanya/async/protocol.d b/source/tanya/async/protocol.d index 4a1b72c..c51dae5 100644 --- a/source/tanya/async/protocol.d +++ b/source/tanya/async/protocol.d @@ -19,7 +19,7 @@ module tanya.async.protocol; import tanya.async.transport; -import tanya.network.socket; +import tanya.net.socket; /** * Common protocol interface. diff --git a/source/tanya/async/transport.d b/source/tanya/async/transport.d index 349abc3..87051e8 100644 --- a/source/tanya/async/transport.d +++ b/source/tanya/async/transport.d @@ -16,7 +16,7 @@ module tanya.async.transport; import tanya.async.protocol; -import tanya.network.socket; +import tanya.net.socket; /** * Base transport interface. diff --git a/source/tanya/async/watcher.d b/source/tanya/async/watcher.d index 8656c94..1159779 100644 --- a/source/tanya/async/watcher.d +++ b/source/tanya/async/watcher.d @@ -20,7 +20,7 @@ import tanya.async.transport; import tanya.container.buffer; import tanya.container.list; import tanya.memory.allocator; -import tanya.network.socket; +import tanya.net.socket; /** * A watcher is an opaque structure that you allocate and register to record diff --git a/source/tanya/net/iface.d b/source/tanya/net/iface.d index 9338745..8e2675a 100644 --- a/source/tanya/net/iface.d +++ b/source/tanya/net/iface.d @@ -142,3 +142,23 @@ String indexToName(uint index) @nogc nothrow @trusted return String(findNullTerminated(buffer)); } } + +/** + * $(D_PSYMBOL AddressFamily) specifies a communication domain; this selects + * the protocol family which will be used for communication. + */ +enum AddressFamily : int +{ + unspec = 0, /// Unspecified. + local = 1, /// Local to host (pipes and file-domain). + unix = local, /// POSIX name for PF_LOCAL. + inet = 2, /// IP protocol family. + ax25 = 3, /// Amateur Radio AX.25. + ipx = 4, /// Novell Internet Protocol. + appletalk = 5, /// Appletalk DDP. + netrom = 6, /// Amateur radio NetROM. + bridge = 7, /// Multiprotocol bridge. + atmpvc = 8, /// ATM PVCs. + x25 = 9, /// Reserved for X.25 project. + inet6 = 10, /// IP version 6. +} diff --git a/source/tanya/net/ip.d b/source/tanya/net/ip.d index 8299f73..54a27e3 100644 --- a/source/tanya/net/ip.d +++ b/source/tanya/net/ip.d @@ -14,7 +14,6 @@ */ module tanya.net.ip; -import core.sys.posix.sys.socket; import std.algorithm.comparison; import std.ascii; import std.typecons; @@ -1331,7 +1330,7 @@ struct Address */ struct Endpoint { - private sa_family_t family = AF_UNSPEC; + private AddressFamily family = AddressFamily.unspec; private ubyte[ushort.sizeof] service; private Address4 address4; // Unused sin6_flowinfo if IPv6 private Address6 address6; // Unused if IPv4 @@ -1345,10 +1344,12 @@ struct Endpoint * Constructs an endpoint. * * Params: + * T = Address type (IPv4 or IPv6). * address = IP address that should be associated with the endpoint. * port = Port number in network byte order. */ - this(Address address, const ushort port = anyPort) + this(T)(T address, const ushort port = anyPort) + if (is(T == Address) || is(T == Address4) || is(T == Address6)) { this.address = address; this.port = port; @@ -1357,7 +1358,7 @@ struct Endpoint /** * Returns: Port number in network byte order. */ - @property ushort port() const @nogc nothrow pure @safe + @property inout(ushort) port() inout const @nogc nothrow pure @safe { return this.service[].toHostOrder!ushort(); } @@ -1374,13 +1375,13 @@ struct Endpoint /** * Returns: IP address associated with the endpoint. */ - @property Address address() @nogc nothrow pure @safe + @property inout(Address) address() inout @nogc nothrow pure @safe { - if (this.family == AF_INET) + if (this.family == AddressFamily.inet) { return Address(this.address4); } - else if (this.family == AF_INET6) + else if (this.family == AddressFamily.inet6) { return Address(this.address6); } @@ -1395,14 +1396,26 @@ struct Endpoint { if (address.isV4()) { - this.family = AF_INET; - this.address4 = address.toV4(); + this.address = address.toV4(); } else if (address.isV6()) { - this.family = AF_INET6; - this.address4 = Address4(0); - this.address6 = address.toV6(); + this.address = address.toV6(); } } + + /// ditto + @property void address(Address4 address) @nogc nothrow pure @safe + { + this.family = AddressFamily.inet; + this.address4 = address; + } + + /// ditto + @property void address(Address6 address) @nogc nothrow pure @safe + { + this.family = AddressFamily.inet6; + this.address4 = Address4(0); + this.address6 = address; + } } diff --git a/source/tanya/net/package.d b/source/tanya/net/package.d index 536715c..37ac2cc 100644 --- a/source/tanya/net/package.d +++ b/source/tanya/net/package.d @@ -17,4 +17,5 @@ module tanya.net; public import tanya.net.iface; public import tanya.net.inet; public import tanya.net.ip; +public import tanya.net.socket; public import tanya.net.uri; diff --git a/source/tanya/net/socket.d b/source/tanya/net/socket.d new file mode 100644 index 0000000..0bdb84a --- /dev/null +++ b/source/tanya/net/socket.d @@ -0,0 +1,1258 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Low-level socket programming. + * + * Current API supports only server-side TCP communication. + * + * For an example of an asynchronous server refer to the documentation of the + * $(D_PSYMBOL tanya.async.loop) module. + * + * Copyright: Eugene Wissner 2016-2020. + * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, + * Mozilla Public License, v. 2.0). + * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) + * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/network/socket.d, + * tanya/network/socket.d) + */ +module tanya.net.socket; + +import core.stdc.errno; +import core.time; +public import std.socket : SocketOption, SocketOptionLevel; +import tanya.bitmanip; +import tanya.memory.allocator; +import tanya.meta.trait; +import tanya.os.error; +import tanya.net.iface; +import tanya.net.ip; + +/// Value returned by socket operations on error. +enum int socketError = -1; + +version (Posix) +{ + import core.stdc.errno; + import core.sys.posix.fcntl; + import core.sys.posix.netdb; + import core.sys.posix.netinet.in_; + import core.sys.posix.sys.socket; + import core.sys.posix.sys.time; + import core.sys.posix.unistd; + + enum SocketType : int + { + init = -1, + } + + private alias LingerField = int; +} +else version (Windows) +{ + import core.sys.windows.winbase; + import core.sys.windows.winerror; + import core.sys.windows.winsock2 : accept, + addrinfo, + bind, + closesocket, + FIONBIO, + freeaddrinfo, + getaddrinfo, + getsockopt, + ioctlsocket, + listen, + MSG_DONTROUTE, + MSG_OOB, + MSG_PEEK, + recv, + SD_BOTH, + SD_RECEIVE, + SD_SEND, + send, + setsockopt, + shutdown, + SO_TYPE, + SOCKADDR, + sockaddr, + sockaddr_in, + sockaddr_in6, + SOCKADDR_STORAGE, + socket, + socklen_t, + SOL_SOCKET, + WSAEWOULDBLOCK, + WSAGetLastError; + import tanya.async.iocp; + import tanya.sys.windows.def; + public import tanya.sys.windows.winbase; + public import tanya.sys.windows.winsock2; + + enum SocketType : size_t + { + init = ~0, + } + + private alias LingerField = ushort; + + enum OverlappedSocketEvent + { + accept = 1, + read = 2, + write = 3, + } + + class SocketState : State + { + private WSABUF buffer; + } + + /** + * Socket returned if a connection has been established. + * + * Note: Available only on Windows. + */ + class OverlappedConnectedSocket : ConnectedSocket + { + /** + * Create a socket. + * + * Params: + * handle = Socket handle. + * af = Address family. + */ + this(SocketType handle, AddressFamily af) @nogc + { + super(handle, af); + } + + /** + * Begins to asynchronously receive data from a connected socket. + * + * Params: + * buffer = Storage location for the received data. + * flags = Flags. + * overlapped = Unique operation identifier. + * + * Returns: $(D_KEYWORD true) if the operation could be finished synchronously. + * $(D_KEYWORD false) otherwise. + * + * Throws: $(D_PSYMBOL SocketException) if unable to receive. + */ + bool beginReceive(ubyte[] buffer, + SocketState overlapped, + Flags flags = Flags(Flag.none)) @nogc @trusted + { + auto receiveFlags = cast(DWORD) flags; + + overlapped.handle = cast(HANDLE) handle_; + overlapped.event = OverlappedSocketEvent.read; + overlapped.buffer.len = cast(ULONG) buffer.length; + overlapped.buffer.buf = cast(char*) buffer.ptr; + + auto result = WSARecv(handle_, + cast(WSABUF*) &overlapped.buffer, + 1u, + null, + &receiveFlags, + &overlapped.overlapped, + null); + + if (result == socketError && !wouldHaveBlocked) + { + throw defaultAllocator.make!SocketException("Unable to receive"); + } + return result == 0; + } + + /** + * Ends a pending asynchronous read. + * + * Params: + * overlapped = Unique operation identifier. + * + * Returns: Number of bytes received. + * + * Throws: $(D_PSYMBOL SocketException) if unable to receive. + * + * Postcondition: $(D_INLINECODE result >= 0). + */ + int endReceive(SocketState overlapped) @nogc @trusted + out (count) + { + assert(count >= 0); + } + do + { + DWORD lpNumber; + BOOL result = GetOverlappedResult(overlapped.handle, + &overlapped.overlapped, + &lpNumber, + FALSE); + if (result == FALSE && !wouldHaveBlocked) + { + disconnected_ = true; + throw defaultAllocator.make!SocketException("Unable to receive"); + } + if (lpNumber == 0) + { + disconnected_ = true; + } + return lpNumber; + } + + /** + * Sends data asynchronously to a connected socket. + * + * Params: + * buffer = Data to be sent. + * flags = Flags. + * overlapped = Unique operation identifier. + * + * Returns: $(D_KEYWORD true) if the operation could be finished synchronously. + * $(D_KEYWORD false) otherwise. + * + * Throws: $(D_PSYMBOL SocketException) if unable to send. + */ + bool beginSend(ubyte[] buffer, + SocketState overlapped, + Flags flags = Flags(Flag.none)) @nogc @trusted + { + overlapped.handle = cast(HANDLE) handle_; + overlapped.event = OverlappedSocketEvent.write; + overlapped.buffer.len = cast(ULONG) buffer.length; + overlapped.buffer.buf = cast(char*) buffer.ptr; + + auto result = WSASend(handle_, + &overlapped.buffer, + 1u, + null, + cast(DWORD) flags, + &overlapped.overlapped, + null); + + if (result == socketError && !wouldHaveBlocked) + { + disconnected_ = true; + throw defaultAllocator.make!SocketException("Unable to send"); + } + return result == 0; + } + + /** + * Ends a pending asynchronous send. + * + * Params: + * overlapped = Unique operation identifier. + * + * Returns: Number of bytes sent. + * + * Throws: $(D_PSYMBOL SocketException) if unable to receive. + * + * Postcondition: $(D_INLINECODE result >= 0). + */ + int endSend(SocketState overlapped) @nogc @trusted + out (count) + { + assert(count >= 0); + } + do + { + DWORD lpNumber; + BOOL result = GetOverlappedResult(overlapped.handle, + &overlapped.overlapped, + &lpNumber, + FALSE); + if (result == FALSE && !wouldHaveBlocked) + { + disconnected_ = true; + throw defaultAllocator.make!SocketException("Unable to receive"); + } + return lpNumber; + } + } + + /** + * Windows stream socket overlapped I/O. + */ + class OverlappedStreamSocket : StreamSocket + { + // Accept extension function pointer. + package LPFN_ACCEPTEX acceptExtension; + + /** + * Create a socket. + * + * Params: + * af = Address family. + * + * Throws: $(D_PSYMBOL SocketException) on errors. + */ + this(AddressFamily af) @nogc @trusted + { + super(af); + scope (failure) + { + this.close(); + } + blocking = false; + + GUID guidAcceptEx = WSAID_ACCEPTEX; + DWORD dwBytes; + + auto result = WSAIoctl(handle_, + SIO_GET_EXTENSION_FUNCTION_POINTER, + &guidAcceptEx, + guidAcceptEx.sizeof, + &acceptExtension, + acceptExtension.sizeof, + &dwBytes, + null, + null); + if (!result == socketError) + { + throw make!SocketException(defaultAllocator, + "Unable to retrieve an accept extension function pointer"); + } + } + + /** + * Begins an asynchronous operation to accept an incoming connection attempt. + * + * Params: + * overlapped = Unique operation identifier. + * + * Returns: $(D_KEYWORD true) if the operation could be finished synchronously. + * $(D_KEYWORD false) otherwise. + * + * Throws: $(D_PSYMBOL SocketException) on accept errors. + */ + bool beginAccept(SocketState overlapped) @nogc @trusted + { + auto socket = cast(SocketType) socket(addressFamily, 1, 0); + if (socket == SocketType.init) + { + throw defaultAllocator.make!SocketException("Unable to create socket"); + } + scope (failure) + { + closesocket(socket); + } + DWORD dwBytes; + overlapped.handle = cast(HANDLE) socket; + overlapped.event = OverlappedSocketEvent.accept; + + const len = (sockaddr_in.sizeof + 16) * 2; + overlapped.buffer.len = len; + overlapped.buffer.buf = cast(char*) defaultAllocator.allocate(len).ptr; + + // We don't want to get any data now, but only start to accept the connections + BOOL result = acceptExtension(handle_, + socket, + overlapped.buffer.buf, + 0u, + sockaddr_in.sizeof + 16, + sockaddr_in.sizeof + 16, + &dwBytes, + &overlapped.overlapped); + if (result == FALSE && !wouldHaveBlocked) + { + throw defaultAllocator.make!SocketException("Unable to accept socket connection"); + } + return result == TRUE; + } + + /** + * Asynchronously accepts an incoming connection attempt and creates a + * new socket to handle remote host communication. + * + * Params: + * overlapped = Unique operation identifier. + * + * Returns: Connected socket. + * + * Throws: $(D_PSYMBOL SocketException) if unable to accept. + */ + OverlappedConnectedSocket endAccept(SocketState overlapped) + @nogc @trusted + { + scope (exit) + { + defaultAllocator.dispose(overlapped.buffer.buf[0 .. overlapped.buffer.len]); + } + auto socket = make!OverlappedConnectedSocket(defaultAllocator, + cast(SocketType) overlapped.handle, + addressFamily); + scope (failure) + { + defaultAllocator.dispose(socket); + } + socket.setOption(SocketOptionLevel.SOCKET, + cast(SocketOption) SO_UPDATE_ACCEPT_CONTEXT, + cast(size_t) handle); + return socket; + } + } +} + +/** + * Socket option that specifies what should happen when the socket that + * promises reliable delivery still has untransmitted messages when + * it is closed. + */ +struct Linger +{ + /// If nonzero, $(D_PSYMBOL close) and $(D_PSYMBOL shutdown) block until + /// the data are transmitted or the timeout period has expired. + LingerField l_onoff; + + /// Time, in seconds to wait before any buffered data to be sent is + /// discarded. + LingerField l_linger; + + /** + * If $(D_PARAM timeout) is `0`, linger is disabled, otherwise enables the + * linger and sets the timeout. + * + * Params: + * timeout = Timeout, in seconds. + */ + this(const ushort timeout) + { + time = timeout; + } + + /// + unittest + { + { + auto linger = Linger(5); + assert(linger.enabled); + assert(linger.time == 5); + } + { + auto linger = Linger(0); + assert(!linger.enabled); + } + { // Default constructor. + Linger linger; + assert(!linger.enabled); + } + } + + /** + * System dependent constructor. + * + * Params: + * l_onoff = $(D_PSYMBOL l_onoff) value. + * l_linger = $(D_PSYMBOL l_linger) value. + */ + this(LingerField l_onoff, LingerField l_linger) + { + this.l_onoff = l_onoff; + this.l_linger = l_linger; + } + + /// + unittest + { + auto linger = Linger(1, 5); + assert(linger.l_onoff == 1); + assert(linger.l_linger == 5); + } + + /** + * Params: + * value = Whether to linger after the socket is closed. + * + * See_Also: $(D_PSYMBOL time). + */ + @property void enabled(const bool value) pure nothrow @safe @nogc + { + this.l_onoff = value; + } + + /** + * Returns: Whether to linger after the socket is closed. + */ + @property bool enabled() const pure nothrow @safe @nogc + { + return this.l_onoff != 0; + } + + /** + * Returns: Timeout period, in seconds, to wait before closing the socket + * if the $(D_PSYMBOL Linger) is $(D_PSYMBOL enabled). + */ + @property ushort time() const pure nothrow @safe @nogc + { + return this.l_linger & ushort.max; + } + + /** + * Sets timeout period, to wait before closing the socket if the + * $(D_PSYMBOL Linger) is $(D_PSYMBOL enabled), ignored otherwise. + * + * Params: + * timeout = Timeout period, in seconds. + */ + @property void time(const ushort timeout) pure nothrow @safe @nogc + { + this.l_onoff = timeout > 0; + this.l_linger = timeout; + } +} + +version (linux) +{ + enum SOCK_NONBLOCK = O_NONBLOCK; + extern(C) int accept4(int, sockaddr*, socklen_t*, int flags) @nogc nothrow; +} +else version (OSX) +{ + version = MacBSD; +} +else version (iOS) +{ + version = MacBSD; +} +else version (FreeBSD) +{ + version = MacBSD; +} +else version (OpenBSD) +{ + version = MacBSD; +} +else version (DragonFlyBSD) +{ + version = MacBSD; +} + +version (MacBSD) +{ + enum ESOCKTNOSUPPORT = 44; // Socket type not suppoted. +} + +private immutable +{ + typeof(&getaddrinfo) getaddrinfoPointer; + typeof(&freeaddrinfo) freeaddrinfoPointer; +} + +shared static this() +{ + version (Windows) + { + auto ws2Lib = GetModuleHandle("ws2_32.dll"); + + getaddrinfoPointer = cast(typeof(getaddrinfoPointer)) + GetProcAddress(ws2Lib, "getaddrinfo"); + freeaddrinfoPointer = cast(typeof(freeaddrinfoPointer)) + GetProcAddress(ws2Lib, "freeaddrinfo"); + } + else version (Posix) + { + getaddrinfoPointer = &getaddrinfo; + freeaddrinfoPointer = &freeaddrinfo; + } +} + +/** + * $(D_PSYMBOL SocketException) should be thrown only if one of the socket functions + * $(D_PSYMBOL socketError) and sets $(D_PSYMBOL errno), because + * $(D_PSYMBOL SocketException) relies on the $(D_PSYMBOL errno) value. + */ +class SocketException : Exception +{ + const ErrorCode.ErrorNo error = ErrorCode.ErrorNo.success; + + /** + * Params: + * msg = The message for the exception. + * file = The file where the exception occurred. + * line = The line number where the exception occurred. + * next = The previous exception in the chain of exceptions, if any. + */ + this(string msg, + string file = __FILE__, + size_t line = __LINE__, + Throwable next = null) @nogc @safe nothrow + { + super(msg, file, line, next); + + foreach (member; EnumMembers!(ErrorCode.ErrorNo)) + { + if (member == lastError) + { + error = member; + return; + } + } + if (lastError == ENOMEM) + { + error = ErrorCode.ErrorNo.noBufferSpace; + } + else if (lastError == EMFILE) + { + error = ErrorCode.ErrorNo.tooManyDescriptors; + } + else version (linux) + { + if (lastError == ENOSR) + { + error = ErrorCode.ErrorNo.networkDown; + } + } + else version (Posix) + { + if (lastError == EPROTO) + { + error = ErrorCode.ErrorNo.networkDown; + } + } + } +} + +/** + * Class for creating a network communication endpoint using the Berkeley + * sockets interfaces of different types. + */ +abstract class Socket +{ + version (Posix) + { + /** + * How a socket is shutdown. + */ + enum Shutdown : int + { + receive = SHUT_RD, /// Socket receives are disallowed + send = SHUT_WR, /// Socket sends are disallowed + both = SHUT_RDWR, /// Both receive and send + } + } + else version (Windows) + { + /// Property to get or set whether the socket is blocking or nonblocking. + private bool blocking_ = true; + + /** + * How a socket is shutdown. + */ + enum Shutdown : int + { + receive = SD_RECEIVE, /// Socket receives are disallowed. + send = SD_SEND, /// Socket sends are disallowed. + both = SD_BOTH, /// Both receive and send. + } + + // The WinSock timeouts seem to be effectively skewed by a constant + // offset of about half a second (in milliseconds). + private enum WINSOCK_TIMEOUT_SKEW = 500; + } + + /// Socket handle. + protected SocketType handle_; + + /// Address family. + protected AddressFamily family; + + private @property void handle(SocketType handle) @nogc + in + { + assert(handle != SocketType.init); + assert(handle_ == SocketType.init, "Socket handle cannot be changed"); + } + do + { + handle_ = handle; + + // Set the option to disable SIGPIPE on send() if the platform + // has it (e.g. on OS X). + static if (is(typeof(SO_NOSIGPIPE))) + { + setOption(SocketOptionLevel.SOCKET, cast(SocketOption)SO_NOSIGPIPE, true); + } + } + + @property inout(SocketType) handle() inout const pure nothrow @safe @nogc + { + return handle_; + } + + /** + * Create a socket. + * + * Params: + * handle = Socket. + * af = Address family. + */ + this(SocketType handle, AddressFamily af) @nogc + in + { + assert(handle != SocketType.init); + } + do + { + scope (failure) + { + this.close(); + } + this.handle = handle; + family = af; + } + + /** + * Closes the socket and calls the destructor on itself. + */ + ~this() nothrow @trusted @nogc + { + this.close(); + } + + /** + * Get a socket option. + * + * Params: + * level = Protocol level at that the option exists. + * option = Option. + * result = Buffer to save the result. + * + * Returns: The number of bytes written to $(D_PARAM result). + * + * Throws: $(D_PSYMBOL SocketException) on error. + */ + protected int getOption(SocketOptionLevel level, + SocketOption option, + void[] result) const @trusted @nogc + { + auto length = cast(socklen_t) result.length; + if (getsockopt(handle_, + cast(int) level, + cast(int) option, + result.ptr, + &length) == socketError) + { + throw defaultAllocator.make!SocketException("Unable to get socket option"); + } + return length; + } + + /// Ditto. + int getOption(SocketOptionLevel level, + SocketOption option, + out size_t result) const @trusted @nogc + { + return getOption(level, option, (&result)[0 .. 1]); + } + + /// Ditto. + int getOption(SocketOptionLevel level, + SocketOption option, + out Linger result) const @trusted @nogc + { + return getOption(level, option, (&result)[0 .. 1]); + } + + /// Ditto. + int getOption(SocketOptionLevel level, + SocketOption option, + out Duration result) const @trusted @nogc + { + // WinSock returns the timeout values as a milliseconds DWORD, + // while Linux and BSD return a timeval struct. + version (Posix) + { + timeval tv; + auto ret = getOption(level, option, (&tv)[0 .. 1]); + result = dur!"seconds"(tv.tv_sec) + dur!"usecs"(tv.tv_usec); + } + else version (Windows) + { + int msecs; + auto ret = getOption(level, option, (&msecs)[0 .. 1]); + if (option == SocketOption.RCVTIMEO) + { + msecs += WINSOCK_TIMEOUT_SKEW; + } + result = dur!"msecs"(msecs); + } + return ret; + } + + /** + * Set a socket option. + * + * Params: + * level = Protocol level at that the option exists. + * option = Option. + * value = Option value. + * + * Throws: $(D_PSYMBOL SocketException) on error. + */ + protected void setOption(SocketOptionLevel level, + SocketOption option, + void[] value) const @trusted @nogc + { + if (setsockopt(handle_, + cast(int)level, + cast(int)option, + value.ptr, + cast(uint) value.length) == socketError) + { + throw defaultAllocator.make!SocketException("Unable to set socket option"); + } + } + + /// Ditto. + void setOption(SocketOptionLevel level, SocketOption option, size_t value) + const @trusted @nogc + { + setOption(level, option, (&value)[0 .. 1]); + } + + /// Ditto. + void setOption(SocketOptionLevel level, SocketOption option, Linger value) + const @trusted @nogc + { + setOption(level, option, (&value)[0 .. 1]); + } + + /// Ditto. + void setOption(SocketOptionLevel level, SocketOption option, Duration value) + const @trusted @nogc + { + version (Posix) + { + timeval tv; + value.split!("seconds", "usecs")(tv.tv_sec, tv.tv_usec); + setOption(level, option, (&tv)[0 .. 1]); + } + else version (Windows) + { + auto msecs = cast(int) value.total!"msecs"; + if (msecs > 0 && option == SocketOption.RCVTIMEO) + { + msecs = max(1, msecs - WINSOCK_TIMEOUT_SKEW); + } + setOption(level, option, msecs); + } + } + + /** + * Returns: Socket's blocking flag. + */ + @property inout(bool) blocking() inout const nothrow @nogc + { + version (Posix) + { + return !(fcntl(handle_, F_GETFL, 0) & O_NONBLOCK); + } + else version (Windows) + { + return this.blocking_; + } + } + + /** + * Params: + * yes = Socket's blocking flag. + */ + @property void blocking(bool yes) @nogc + { + version (Posix) + { + int fl = fcntl(handle_, F_GETFL, 0); + + if (fl != socketError) + { + fl = yes ? fl & ~O_NONBLOCK : fl | O_NONBLOCK; + fl = fcntl(handle_, F_SETFL, fl); + } + if (fl == socketError) + { + throw make!SocketException(defaultAllocator, + "Unable to set socket blocking"); + } + } + else version (Windows) + { + uint num = !yes; + if (ioctlsocket(handle_, FIONBIO, &num) == socketError) + { + throw make!SocketException(defaultAllocator, + "Unable to set socket blocking"); + } + this.blocking_ = yes; + } + } + + /** + * Returns: The socket's address family. + */ + @property AddressFamily addressFamily() const @nogc @safe pure nothrow + { + return family; + } + + /** + * Returns: $(D_KEYWORD true) if this is a valid, alive socket. + */ + @property bool isAlive() @trusted const nothrow @nogc + { + int type; + socklen_t typesize = cast(socklen_t) type.sizeof; + return !getsockopt(handle_, SOL_SOCKET, SO_TYPE, cast(char*)&type, &typesize); + } + + /** + * Disables sends and/or receives. + * + * Params: + * how = What to disable. + * + * See_Also: + * $(D_PSYMBOL Shutdown) + */ + void shutdown(Shutdown how = Shutdown.both) @nogc @trusted const nothrow + { + .shutdown(handle_, cast(int)how); + } + + /** + * Immediately drop any connections and release socket resources. + * Calling $(D_PSYMBOL shutdown) before $(D_PSYMBOL close) is recommended + * for connection-oriented sockets. The $(D_PSYMBOL Socket) object is no + * longer usable after $(D_PSYMBOL close). + */ + void close() nothrow @trusted @nogc + { + version(Windows) + { + .closesocket(handle_); + } + else version(Posix) + { + .close(handle_); + } + handle_ = SocketType.init; + } + + /** + * Listen for an incoming connection. $(D_PSYMBOL bind) must be called before you + * can $(D_PSYMBOL listen). + * + * Params: + * backlog = Request of how many pending incoming connections are + * queued until $(D_PSYMBOL accept)ed. + */ + void listen(int backlog) const @trusted @nogc + { + if (.listen(handle_, backlog) == socketError) + { + throw defaultAllocator.make!SocketException("Unable to listen on socket"); + } + } + + /** + * Compare handles. + * + * Params: + * that = Another handle. + * + * Returns: Comparision result. + */ + int opCmp(size_t that) const pure nothrow @safe @nogc + { + return handle_ < that ? -1 : handle_ > that ? 1 : 0; + } +} + +/** + * Interface with common fileds for stream and connected sockets. + */ +interface ConnectionOrientedSocket +{ + /** + * Flags may be OR'ed together. + */ + enum Flag : int + { + /// No flags specified. + none = 0, + /// Out-of-band stream data. + outOfBand = MSG_OOB, + /// Peek at incoming data without removing it from the queue, only for receiving. + peek = MSG_PEEK, + /// Data should not be subject to routing; this flag may be ignored. Only for sending. + dontRoute = MSG_DONTROUTE, + } + + alias Flags = BitFlags!Flag; +} + +class StreamSocket : Socket, ConnectionOrientedSocket +{ + /** + * Create a socket. + * + * Params: + * af = Address family. + */ + this(AddressFamily af) @trusted @nogc + { + auto handle = cast(SocketType) socket(af, 1, 0); + if (handle == SocketType.init) + { + throw defaultAllocator.make!SocketException("Unable to create socket"); + } + super(handle, af); + } + + /** + * Associate a local address with this socket. + * + * Params: + * endpoint = Local address. + * + * Throws: $(D_PSYMBOL SocketException) if unable to bind. + */ + void bind(const Endpoint endpoint) const @nogc + { + if (.bind(handle_, cast(const(sockaddr)*) &endpoint, Endpoint.sizeof)) + { + throw defaultAllocator.make!SocketException("Unable to bind socket"); + } + } + + /** + * Accept an incoming connection. + * + * The blocking mode is always inherited. + * + * Returns: $(D_PSYMBOL Socket) for the accepted connection or + * $(D_KEYWORD null) if the call would block on a + * non-blocking socket. + * + * Throws: $(D_PSYMBOL SocketException) if unable to accept. + */ + ConnectedSocket accept() @trusted @nogc + { + SocketType sock; + + version (linux) + { + int flags; + if (!blocking) + { + flags |= SOCK_NONBLOCK; + } + sock = cast(SocketType).accept4(handle_, null, null, flags); + } + else + { + sock = cast(SocketType).accept(handle_, null, null); + } + + if (sock == SocketType.init) + { + if (wouldHaveBlocked()) + { + return null; + } + throw make!SocketException(defaultAllocator, + "Unable to accept socket connection"); + } + + auto newSocket = defaultAllocator.make!ConnectedSocket(sock, addressFamily); + + version (linux) + { // Blocking mode already set + } + else version (Posix) + { + if (!blocking) + { + try + { + newSocket.blocking = blocking; + } + catch (SocketException e) + { + defaultAllocator.dispose(newSocket); + throw e; + } + } + } + else version (Windows) + { // Inherits blocking mode + newSocket.blocking_ = blocking; + } + return newSocket; + } +} + +/** + * Socket returned if a connection has been established. + */ +class ConnectedSocket : Socket, ConnectionOrientedSocket +{ + /** + * $(D_KEYWORD true) if the stream socket peer has performed an orderly + * shutdown. + */ + protected bool disconnected_; + + /** + * Returns: $(D_KEYWORD true) if the stream socket peer has performed an orderly + * shutdown. + */ + @property inout(bool) disconnected() inout const pure nothrow @safe @nogc + { + return disconnected_; + } + + /** + * Create a socket. + * + * Params: + * handle = Socket. + * af = Address family. + */ + this(SocketType handle, AddressFamily af) @nogc + { + super(handle, af); + } + + version (Windows) + { + private static int capToMaxBuffer(size_t size) pure nothrow @safe @nogc + { + // Windows uses int instead of size_t for length arguments. + // Luckily, the send/recv functions make no guarantee that + // all the data is sent, so we use that to send at most + // int.max bytes. + return size > size_t (int.max) ? int.max : cast(int) size; + } + } + else + { + private static size_t capToMaxBuffer(size_t size) pure nothrow @safe @nogc + { + return size; + } + } + + /** + * Receive data on the connection. + * + * Params: + * buf = Buffer to save received data. + * flags = Flags. + * + * Returns: The number of bytes received or 0 if nothing received + * because the call would block. + * + * Throws: $(D_PSYMBOL SocketException) if unable to receive. + */ + ptrdiff_t receive(ubyte[] buf, Flags flags = Flag.none) @trusted @nogc + { + ptrdiff_t ret; + if (!buf.length) + { + return 0; + } + + ret = recv(handle_, buf.ptr, capToMaxBuffer(buf.length), cast(int) flags); + if (ret == 0) + { + disconnected_ = true; + } + else if (ret == socketError) + { + if (wouldHaveBlocked()) + { + return 0; + } + disconnected_ = true; + throw defaultAllocator.make!SocketException("Unable to receive"); + } + return ret; + } + + /** + * Send data on the connection. If the socket is blocking and there is no + * buffer space left, $(D_PSYMBOL send) waits, non-blocking socket returns + * 0 in this case. + * + * Params: + * buf = Data to be sent. + * flags = Flags. + * + * Returns: The number of bytes actually sent. + * + * Throws: $(D_PSYMBOL SocketException) if unable to send. + */ + ptrdiff_t send(const(ubyte)[] buf, Flags flags = Flag.none) + const @trusted @nogc + { + int sendFlags = cast(int) flags; + ptrdiff_t sent; + + static if (is(typeof(MSG_NOSIGNAL))) + { + sendFlags |= MSG_NOSIGNAL; + } + + sent = .send(handle_, buf.ptr, capToMaxBuffer(buf.length), sendFlags); + if (sent != socketError) + { + return sent; + } + else if (wouldHaveBlocked()) + { + return 0; + } + throw defaultAllocator.make!SocketException("Unable to send"); + } +} + +/** + * Checks if the last error is a serious error or just a special + * behaviour error of non-blocking sockets (for example an error + * returned because the socket would block or because the + * asynchronous operation was successfully started but not finished yet). + * + * Returns: $(D_KEYWORD false) if a serious error happened, $(D_KEYWORD true) + * otherwise. + */ +bool wouldHaveBlocked() nothrow @trusted @nogc +{ + version (Posix) + { + return errno == EAGAIN || errno == EWOULDBLOCK; + } + else version (Windows) + { + return WSAGetLastError() == ERROR_IO_PENDING + || WSAGetLastError() == WSAEWOULDBLOCK + || WSAGetLastError() == ERROR_IO_INCOMPLETE; + } +} + +/** + * Returns: Platform specific error code. + */ +private @property int lastError() nothrow @safe @nogc +{ + version (Windows) + { + return WSAGetLastError(); + } + else + { + return errno; + } +} diff --git a/source/tanya/network/package.d b/source/tanya/network/package.d deleted file mode 100644 index 8c349ec..0000000 --- a/source/tanya/network/package.d +++ /dev/null @@ -1,17 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * Network programming. - * - * Copyright: Eugene Wissner 2016-2020. - * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, - * Mozilla Public License, v. 2.0). - * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) - * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/network/package.d, - * tanya/network/package.d) - */ -module tanya.network; - -public import tanya.network.socket; diff --git a/source/tanya/network/socket.d b/source/tanya/network/socket.d deleted file mode 100644 index bfd192f..0000000 --- a/source/tanya/network/socket.d +++ /dev/null @@ -1,1458 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * Low-level socket programming. - * - * Current API supports only server-side TCP communication. - * - * Here is an example of a cross-platform blocking server: - * - * --- - * import std.stdio; - * import tanya.memory; - * import tanya.network; - * - * void main() - * { - * auto socket = defaultAllocator.make!StreamSocket(AddressFamily.inet); - * auto address = defaultAllocator.make!InternetAddress("127.0.0.1", - * cast(ushort) 8192); - * - * socket.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true); - * socket.blocking = true; - * socket.bind(address); - * socket.listen(5); - * - * auto client = socket.accept(); - * client.send(cast(const(ubyte)[]) "Test\n"); - * - * ubyte[100] buf; - * auto response = client.receive(buf[]); - * - * writeln(cast(const(char)[]) buf[0 .. response]); - * - * defaultAllocator.dispose(client); - * defaultAllocator.dispose(socket); - * } - * --- - * - * For an example of an asynchronous server refer to the documentation of the - * $(D_PSYMBOL tanya.async.loop) module. - * - * Copyright: Eugene Wissner 2016-2020. - * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, - * Mozilla Public License, v. 2.0). - * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) - * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/network/socket.d, - * tanya/network/socket.d) - */ -module tanya.network.socket; - -import core.stdc.errno; -import core.time; -public import std.socket : SocketOption, SocketOptionLevel; -import tanya.bitmanip; -import tanya.memory.allocator; -import tanya.meta.trait; -import tanya.os.error; - -/// Value returned by socket operations on error. -enum int socketError = -1; - -version (Posix) -{ - import core.stdc.errno; - import core.sys.posix.fcntl; - import core.sys.posix.netdb; - import core.sys.posix.netinet.in_; - import core.sys.posix.sys.socket; - import core.sys.posix.sys.time; - import core.sys.posix.unistd; - - enum SocketType : int - { - init = -1, - } - - private alias LingerField = int; -} -else version (Windows) -{ - import core.sys.windows.winbase; - import core.sys.windows.winerror; - import core.sys.windows.winsock2 : accept, - addrinfo, - bind, - closesocket, - FIONBIO, - freeaddrinfo, - getaddrinfo, - getsockopt, - ioctlsocket, - listen, - MSG_DONTROUTE, - MSG_OOB, - MSG_PEEK, - recv, - SD_BOTH, - SD_RECEIVE, - SD_SEND, - send, - setsockopt, - shutdown, - SO_TYPE, - SOCKADDR, - sockaddr, - sockaddr_in, - sockaddr_in6, - SOCKADDR_STORAGE, - socket, - socklen_t, - SOL_SOCKET, - WSAEWOULDBLOCK, - WSAGetLastError; - import tanya.async.iocp; - import tanya.sys.windows.def; - public import tanya.sys.windows.winbase; - public import tanya.sys.windows.winsock2; - - enum SocketType : size_t - { - init = ~0, - } - - private alias LingerField = ushort; - - enum OverlappedSocketEvent - { - accept = 1, - read = 2, - write = 3, - } - - class SocketState : State - { - private WSABUF buffer; - } - - /** - * Socket returned if a connection has been established. - * - * Note: Available only on Windows. - */ - class OverlappedConnectedSocket : ConnectedSocket - { - /** - * Create a socket. - * - * Params: - * handle = Socket handle. - * af = Address family. - */ - this(SocketType handle, AddressFamily af) @nogc - { - super(handle, af); - } - - /** - * Begins to asynchronously receive data from a connected socket. - * - * Params: - * buffer = Storage location for the received data. - * flags = Flags. - * overlapped = Unique operation identifier. - * - * Returns: $(D_KEYWORD true) if the operation could be finished synchronously. - * $(D_KEYWORD false) otherwise. - * - * Throws: $(D_PSYMBOL SocketException) if unable to receive. - */ - bool beginReceive(ubyte[] buffer, - SocketState overlapped, - Flags flags = Flags(Flag.none)) @nogc @trusted - { - auto receiveFlags = cast(DWORD) flags; - - overlapped.handle = cast(HANDLE) handle_; - overlapped.event = OverlappedSocketEvent.read; - overlapped.buffer.len = cast(ULONG) buffer.length; - overlapped.buffer.buf = cast(char*) buffer.ptr; - - auto result = WSARecv(handle_, - cast(WSABUF*) &overlapped.buffer, - 1u, - null, - &receiveFlags, - &overlapped.overlapped, - null); - - if (result == socketError && !wouldHaveBlocked) - { - throw defaultAllocator.make!SocketException("Unable to receive"); - } - return result == 0; - } - - /** - * Ends a pending asynchronous read. - * - * Params: - * overlapped = Unique operation identifier. - * - * Returns: Number of bytes received. - * - * Throws: $(D_PSYMBOL SocketException) if unable to receive. - * - * Postcondition: $(D_INLINECODE result >= 0). - */ - int endReceive(SocketState overlapped) @nogc @trusted - out (count) - { - assert(count >= 0); - } - do - { - DWORD lpNumber; - BOOL result = GetOverlappedResult(overlapped.handle, - &overlapped.overlapped, - &lpNumber, - FALSE); - if (result == FALSE && !wouldHaveBlocked) - { - disconnected_ = true; - throw defaultAllocator.make!SocketException("Unable to receive"); - } - if (lpNumber == 0) - { - disconnected_ = true; - } - return lpNumber; - } - - /** - * Sends data asynchronously to a connected socket. - * - * Params: - * buffer = Data to be sent. - * flags = Flags. - * overlapped = Unique operation identifier. - * - * Returns: $(D_KEYWORD true) if the operation could be finished synchronously. - * $(D_KEYWORD false) otherwise. - * - * Throws: $(D_PSYMBOL SocketException) if unable to send. - */ - bool beginSend(ubyte[] buffer, - SocketState overlapped, - Flags flags = Flags(Flag.none)) @nogc @trusted - { - overlapped.handle = cast(HANDLE) handle_; - overlapped.event = OverlappedSocketEvent.write; - overlapped.buffer.len = cast(ULONG) buffer.length; - overlapped.buffer.buf = cast(char*) buffer.ptr; - - auto result = WSASend(handle_, - &overlapped.buffer, - 1u, - null, - cast(DWORD) flags, - &overlapped.overlapped, - null); - - if (result == socketError && !wouldHaveBlocked) - { - disconnected_ = true; - throw defaultAllocator.make!SocketException("Unable to send"); - } - return result == 0; - } - - /** - * Ends a pending asynchronous send. - * - * Params: - * overlapped = Unique operation identifier. - * - * Returns: Number of bytes sent. - * - * Throws: $(D_PSYMBOL SocketException) if unable to receive. - * - * Postcondition: $(D_INLINECODE result >= 0). - */ - int endSend(SocketState overlapped) @nogc @trusted - out (count) - { - assert(count >= 0); - } - do - { - DWORD lpNumber; - BOOL result = GetOverlappedResult(overlapped.handle, - &overlapped.overlapped, - &lpNumber, - FALSE); - if (result == FALSE && !wouldHaveBlocked) - { - disconnected_ = true; - throw defaultAllocator.make!SocketException("Unable to receive"); - } - return lpNumber; - } - } - - /** - * Windows stream socket overlapped I/O. - */ - class OverlappedStreamSocket : StreamSocket - { - // Accept extension function pointer. - package LPFN_ACCEPTEX acceptExtension; - - /** - * Create a socket. - * - * Params: - * af = Address family. - * - * Throws: $(D_PSYMBOL SocketException) on errors. - */ - this(AddressFamily af) @nogc @trusted - { - super(af); - scope (failure) - { - this.close(); - } - blocking = false; - - GUID guidAcceptEx = WSAID_ACCEPTEX; - DWORD dwBytes; - - auto result = WSAIoctl(handle_, - SIO_GET_EXTENSION_FUNCTION_POINTER, - &guidAcceptEx, - guidAcceptEx.sizeof, - &acceptExtension, - acceptExtension.sizeof, - &dwBytes, - null, - null); - if (!result == socketError) - { - throw make!SocketException(defaultAllocator, - "Unable to retrieve an accept extension function pointer"); - } - } - - /** - * Begins an asynchronous operation to accept an incoming connection attempt. - * - * Params: - * overlapped = Unique operation identifier. - * - * Returns: $(D_KEYWORD true) if the operation could be finished synchronously. - * $(D_KEYWORD false) otherwise. - * - * Throws: $(D_PSYMBOL SocketException) on accept errors. - */ - bool beginAccept(SocketState overlapped) @nogc @trusted - { - auto socket = cast(SocketType) socket(addressFamily, 1, 0); - if (socket == SocketType.init) - { - throw defaultAllocator.make!SocketException("Unable to create socket"); - } - scope (failure) - { - closesocket(socket); - } - DWORD dwBytes; - overlapped.handle = cast(HANDLE) socket; - overlapped.event = OverlappedSocketEvent.accept; - - const len = (sockaddr_in.sizeof + 16) * 2; - overlapped.buffer.len = len; - overlapped.buffer.buf = cast(char*) defaultAllocator.allocate(len).ptr; - - // We don't want to get any data now, but only start to accept the connections - BOOL result = acceptExtension(handle_, - socket, - overlapped.buffer.buf, - 0u, - sockaddr_in.sizeof + 16, - sockaddr_in.sizeof + 16, - &dwBytes, - &overlapped.overlapped); - if (result == FALSE && !wouldHaveBlocked) - { - throw defaultAllocator.make!SocketException("Unable to accept socket connection"); - } - return result == TRUE; - } - - /** - * Asynchronously accepts an incoming connection attempt and creates a - * new socket to handle remote host communication. - * - * Params: - * overlapped = Unique operation identifier. - * - * Returns: Connected socket. - * - * Throws: $(D_PSYMBOL SocketException) if unable to accept. - */ - OverlappedConnectedSocket endAccept(SocketState overlapped) - @nogc @trusted - { - scope (exit) - { - defaultAllocator.dispose(overlapped.buffer.buf[0 .. overlapped.buffer.len]); - } - auto socket = make!OverlappedConnectedSocket(defaultAllocator, - cast(SocketType) overlapped.handle, - addressFamily); - scope (failure) - { - defaultAllocator.dispose(socket); - } - socket.setOption(SocketOptionLevel.SOCKET, - cast(SocketOption) SO_UPDATE_ACCEPT_CONTEXT, - cast(size_t) handle); - return socket; - } - } -} - -/** - * Socket option that specifies what should happen when the socket that - * promises reliable delivery still has untransmitted messages when - * it is closed. - */ -struct Linger -{ - /// If nonzero, $(D_PSYMBOL close) and $(D_PSYMBOL shutdown) block until - /// the data are transmitted or the timeout period has expired. - LingerField l_onoff; - - /// Time, in seconds to wait before any buffered data to be sent is - /// discarded. - LingerField l_linger; - - /** - * If $(D_PARAM timeout) is `0`, linger is disabled, otherwise enables the - * linger and sets the timeout. - * - * Params: - * timeout = Timeout, in seconds. - */ - this(const ushort timeout) - { - time = timeout; - } - - /// - unittest - { - { - auto linger = Linger(5); - assert(linger.enabled); - assert(linger.time == 5); - } - { - auto linger = Linger(0); - assert(!linger.enabled); - } - { // Default constructor. - Linger linger; - assert(!linger.enabled); - } - } - - /** - * System dependent constructor. - * - * Params: - * l_onoff = $(D_PSYMBOL l_onoff) value. - * l_linger = $(D_PSYMBOL l_linger) value. - */ - this(LingerField l_onoff, LingerField l_linger) - { - this.l_onoff = l_onoff; - this.l_linger = l_linger; - } - - /// - unittest - { - auto linger = Linger(1, 5); - assert(linger.l_onoff == 1); - assert(linger.l_linger == 5); - } - - /** - * Params: - * value = Whether to linger after the socket is closed. - * - * See_Also: $(D_PSYMBOL time). - */ - @property void enabled(const bool value) pure nothrow @safe @nogc - { - this.l_onoff = value; - } - - /** - * Returns: Whether to linger after the socket is closed. - */ - @property bool enabled() const pure nothrow @safe @nogc - { - return this.l_onoff != 0; - } - - /** - * Returns: Timeout period, in seconds, to wait before closing the socket - * if the $(D_PSYMBOL Linger) is $(D_PSYMBOL enabled). - */ - @property ushort time() const pure nothrow @safe @nogc - { - return this.l_linger & ushort.max; - } - - /** - * Sets timeout period, to wait before closing the socket if the - * $(D_PSYMBOL Linger) is $(D_PSYMBOL enabled), ignored otherwise. - * - * Params: - * timeout = Timeout period, in seconds. - */ - @property void time(const ushort timeout) pure nothrow @safe @nogc - { - this.l_onoff = timeout > 0; - this.l_linger = timeout; - } -} - -version (linux) -{ - enum SOCK_NONBLOCK = O_NONBLOCK; - extern(C) int accept4(int, sockaddr*, socklen_t*, int flags) @nogc nothrow; -} -else version (OSX) -{ - version = MacBSD; -} -else version (iOS) -{ - version = MacBSD; -} -else version (FreeBSD) -{ - version = MacBSD; -} -else version (OpenBSD) -{ - version = MacBSD; -} -else version (DragonFlyBSD) -{ - version = MacBSD; -} - -version (MacBSD) -{ - enum ESOCKTNOSUPPORT = 44; // Socket type not suppoted. -} - -private immutable -{ - typeof(&getaddrinfo) getaddrinfoPointer; - typeof(&freeaddrinfo) freeaddrinfoPointer; -} - -shared static this() -{ - version (Windows) - { - auto ws2Lib = GetModuleHandle("ws2_32.dll"); - - getaddrinfoPointer = cast(typeof(getaddrinfoPointer)) - GetProcAddress(ws2Lib, "getaddrinfo"); - freeaddrinfoPointer = cast(typeof(freeaddrinfoPointer)) - GetProcAddress(ws2Lib, "freeaddrinfo"); - } - else version (Posix) - { - getaddrinfoPointer = &getaddrinfo; - freeaddrinfoPointer = &freeaddrinfo; - } -} - -/** - * $(D_PSYMBOL AddressFamily) specifies a communication domain; this selects - * the protocol family which will be used for communication. - */ -enum AddressFamily : int -{ - unspec = 0, /// Unspecified. - local = 1, /// Local to host (pipes and file-domain). - unix = local, /// POSIX name for PF_LOCAL. - inet = 2, /// IP protocol family. - ax25 = 3, /// Amateur Radio AX.25. - ipx = 4, /// Novell Internet Protocol. - appletalk = 5, /// Appletalk DDP. - netrom = 6, /// Amateur radio NetROM. - bridge = 7, /// Multiprotocol bridge. - atmpvc = 8, /// ATM PVCs. - x25 = 9, /// Reserved for X.25 project. - inet6 = 10, /// IP version 6. -} - -/** - * $(D_PSYMBOL SocketException) should be thrown only if one of the socket functions - * $(D_PSYMBOL socketError) and sets $(D_PSYMBOL errno), because - * $(D_PSYMBOL SocketException) relies on the $(D_PSYMBOL errno) value. - */ -class SocketException : Exception -{ - const ErrorCode.ErrorNo error = ErrorCode.ErrorNo.success; - - /** - * Params: - * msg = The message for the exception. - * file = The file where the exception occurred. - * line = The line number where the exception occurred. - * next = The previous exception in the chain of exceptions, if any. - */ - this(string msg, - string file = __FILE__, - size_t line = __LINE__, - Throwable next = null) @nogc @safe nothrow - { - super(msg, file, line, next); - - foreach (member; EnumMembers!(ErrorCode.ErrorNo)) - { - if (member == lastError) - { - error = member; - return; - } - } - if (lastError == ENOMEM) - { - error = ErrorCode.ErrorNo.noBufferSpace; - } - else if (lastError == EMFILE) - { - error = ErrorCode.ErrorNo.tooManyDescriptors; - } - else version (linux) - { - if (lastError == ENOSR) - { - error = ErrorCode.ErrorNo.networkDown; - } - } - else version (Posix) - { - if (lastError == EPROTO) - { - error = ErrorCode.ErrorNo.networkDown; - } - } - } -} - -/** - * Class for creating a network communication endpoint using the Berkeley - * sockets interfaces of different types. - */ -abstract class Socket -{ - version (Posix) - { - /** - * How a socket is shutdown. - */ - enum Shutdown : int - { - receive = SHUT_RD, /// Socket receives are disallowed - send = SHUT_WR, /// Socket sends are disallowed - both = SHUT_RDWR, /// Both receive and send - } - } - else version (Windows) - { - /// Property to get or set whether the socket is blocking or nonblocking. - private bool blocking_ = true; - - /** - * How a socket is shutdown. - */ - enum Shutdown : int - { - receive = SD_RECEIVE, /// Socket receives are disallowed. - send = SD_SEND, /// Socket sends are disallowed. - both = SD_BOTH, /// Both receive and send. - } - - // The WinSock timeouts seem to be effectively skewed by a constant - // offset of about half a second (in milliseconds). - private enum WINSOCK_TIMEOUT_SKEW = 500; - } - - /// Socket handle. - protected SocketType handle_; - - /// Address family. - protected AddressFamily family; - - private @property void handle(SocketType handle) @nogc - in - { - assert(handle != SocketType.init); - assert(handle_ == SocketType.init, "Socket handle cannot be changed"); - } - do - { - handle_ = handle; - - // Set the option to disable SIGPIPE on send() if the platform - // has it (e.g. on OS X). - static if (is(typeof(SO_NOSIGPIPE))) - { - setOption(SocketOptionLevel.SOCKET, cast(SocketOption)SO_NOSIGPIPE, true); - } - } - - @property inout(SocketType) handle() inout const pure nothrow @safe @nogc - { - return handle_; - } - - /** - * Create a socket. - * - * Params: - * handle = Socket. - * af = Address family. - */ - this(SocketType handle, AddressFamily af) @nogc - in - { - assert(handle != SocketType.init); - } - do - { - scope (failure) - { - this.close(); - } - this.handle = handle; - family = af; - } - - /** - * Closes the socket and calls the destructor on itself. - */ - ~this() nothrow @trusted @nogc - { - this.close(); - } - - /** - * Get a socket option. - * - * Params: - * level = Protocol level at that the option exists. - * option = Option. - * result = Buffer to save the result. - * - * Returns: The number of bytes written to $(D_PARAM result). - * - * Throws: $(D_PSYMBOL SocketException) on error. - */ - protected int getOption(SocketOptionLevel level, - SocketOption option, - void[] result) const @trusted @nogc - { - auto length = cast(socklen_t) result.length; - if (getsockopt(handle_, - cast(int) level, - cast(int) option, - result.ptr, - &length) == socketError) - { - throw defaultAllocator.make!SocketException("Unable to get socket option"); - } - return length; - } - - /// Ditto. - int getOption(SocketOptionLevel level, - SocketOption option, - out size_t result) const @trusted @nogc - { - return getOption(level, option, (&result)[0 .. 1]); - } - - /// Ditto. - int getOption(SocketOptionLevel level, - SocketOption option, - out Linger result) const @trusted @nogc - { - return getOption(level, option, (&result)[0 .. 1]); - } - - /// Ditto. - int getOption(SocketOptionLevel level, - SocketOption option, - out Duration result) const @trusted @nogc - { - // WinSock returns the timeout values as a milliseconds DWORD, - // while Linux and BSD return a timeval struct. - version (Posix) - { - timeval tv; - auto ret = getOption(level, option, (&tv)[0 .. 1]); - result = dur!"seconds"(tv.tv_sec) + dur!"usecs"(tv.tv_usec); - } - else version (Windows) - { - int msecs; - auto ret = getOption(level, option, (&msecs)[0 .. 1]); - if (option == SocketOption.RCVTIMEO) - { - msecs += WINSOCK_TIMEOUT_SKEW; - } - result = dur!"msecs"(msecs); - } - return ret; - } - - /** - * Set a socket option. - * - * Params: - * level = Protocol level at that the option exists. - * option = Option. - * value = Option value. - * - * Throws: $(D_PSYMBOL SocketException) on error. - */ - protected void setOption(SocketOptionLevel level, - SocketOption option, - void[] value) const @trusted @nogc - { - if (setsockopt(handle_, - cast(int)level, - cast(int)option, - value.ptr, - cast(uint) value.length) == socketError) - { - throw defaultAllocator.make!SocketException("Unable to set socket option"); - } - } - - /// Ditto. - void setOption(SocketOptionLevel level, SocketOption option, size_t value) - const @trusted @nogc - { - setOption(level, option, (&value)[0 .. 1]); - } - - /// Ditto. - void setOption(SocketOptionLevel level, SocketOption option, Linger value) - const @trusted @nogc - { - setOption(level, option, (&value)[0 .. 1]); - } - - /// Ditto. - void setOption(SocketOptionLevel level, SocketOption option, Duration value) - const @trusted @nogc - { - version (Posix) - { - timeval tv; - value.split!("seconds", "usecs")(tv.tv_sec, tv.tv_usec); - setOption(level, option, (&tv)[0 .. 1]); - } - else version (Windows) - { - auto msecs = cast(int) value.total!"msecs"; - if (msecs > 0 && option == SocketOption.RCVTIMEO) - { - msecs = max(1, msecs - WINSOCK_TIMEOUT_SKEW); - } - setOption(level, option, msecs); - } - } - - /** - * Returns: Socket's blocking flag. - */ - @property inout(bool) blocking() inout const nothrow @nogc - { - version (Posix) - { - return !(fcntl(handle_, F_GETFL, 0) & O_NONBLOCK); - } - else version (Windows) - { - return this.blocking_; - } - } - - /** - * Params: - * yes = Socket's blocking flag. - */ - @property void blocking(bool yes) @nogc - { - version (Posix) - { - int fl = fcntl(handle_, F_GETFL, 0); - - if (fl != socketError) - { - fl = yes ? fl & ~O_NONBLOCK : fl | O_NONBLOCK; - fl = fcntl(handle_, F_SETFL, fl); - } - if (fl == socketError) - { - throw make!SocketException(defaultAllocator, - "Unable to set socket blocking"); - } - } - else version (Windows) - { - uint num = !yes; - if (ioctlsocket(handle_, FIONBIO, &num) == socketError) - { - throw make!SocketException(defaultAllocator, - "Unable to set socket blocking"); - } - this.blocking_ = yes; - } - } - - /** - * Returns: The socket's address family. - */ - @property AddressFamily addressFamily() const @nogc @safe pure nothrow - { - return family; - } - - /** - * Returns: $(D_KEYWORD true) if this is a valid, alive socket. - */ - @property bool isAlive() @trusted const nothrow @nogc - { - int type; - socklen_t typesize = cast(socklen_t) type.sizeof; - return !getsockopt(handle_, SOL_SOCKET, SO_TYPE, cast(char*)&type, &typesize); - } - - /** - * Disables sends and/or receives. - * - * Params: - * how = What to disable. - * - * See_Also: - * $(D_PSYMBOL Shutdown) - */ - void shutdown(Shutdown how = Shutdown.both) @nogc @trusted const nothrow - { - .shutdown(handle_, cast(int)how); - } - - /** - * Immediately drop any connections and release socket resources. - * Calling $(D_PSYMBOL shutdown) before $(D_PSYMBOL close) is recommended - * for connection-oriented sockets. The $(D_PSYMBOL Socket) object is no - * longer usable after $(D_PSYMBOL close). - */ - void close() nothrow @trusted @nogc - { - version(Windows) - { - .closesocket(handle_); - } - else version(Posix) - { - .close(handle_); - } - handle_ = SocketType.init; - } - - /** - * Listen for an incoming connection. $(D_PSYMBOL bind) must be called before you - * can $(D_PSYMBOL listen). - * - * Params: - * backlog = Request of how many pending incoming connections are - * queued until $(D_PSYMBOL accept)ed. - */ - void listen(int backlog) const @trusted @nogc - { - if (.listen(handle_, backlog) == socketError) - { - throw defaultAllocator.make!SocketException("Unable to listen on socket"); - } - } - - /** - * Compare handles. - * - * Params: - * that = Another handle. - * - * Returns: Comparision result. - */ - int opCmp(size_t that) const pure nothrow @safe @nogc - { - return handle_ < that ? -1 : handle_ > that ? 1 : 0; - } -} - -/** - * Interface with common fileds for stream and connected sockets. - */ -interface ConnectionOrientedSocket -{ - /** - * Flags may be OR'ed together. - */ - enum Flag : int - { - /// No flags specified. - none = 0, - /// Out-of-band stream data. - outOfBand = MSG_OOB, - /// Peek at incoming data without removing it from the queue, only for receiving. - peek = MSG_PEEK, - /// Data should not be subject to routing; this flag may be ignored. Only for sending. - dontRoute = MSG_DONTROUTE, - } - - alias Flags = BitFlags!Flag; -} - -class StreamSocket : Socket, ConnectionOrientedSocket -{ - /** - * Create a socket. - * - * Params: - * af = Address family. - */ - this(AddressFamily af) @trusted @nogc - { - auto handle = cast(SocketType) socket(af, 1, 0); - if (handle == SocketType.init) - { - throw defaultAllocator.make!SocketException("Unable to create socket"); - } - super(handle, af); - } - - /** - * Associate a local address with this socket. - * - * Params: - * address = Local address. - * - * Throws: $(D_PSYMBOL SocketException) if unable to bind. - */ - void bind(Address address) const @trusted @nogc - { - if (.bind(handle_, address.name, address.length) == socketError) - { - throw defaultAllocator.make!SocketException("Unable to bind socket"); - } - } - - /** - * Accept an incoming connection. - * - * The blocking mode is always inherited. - * - * Returns: $(D_PSYMBOL Socket) for the accepted connection or - * $(D_KEYWORD null) if the call would block on a - * non-blocking socket. - * - * Throws: $(D_PSYMBOL SocketException) if unable to accept. - */ - ConnectedSocket accept() @trusted @nogc - { - SocketType sock; - - version (linux) - { - int flags; - if (!blocking) - { - flags |= SOCK_NONBLOCK; - } - sock = cast(SocketType).accept4(handle_, null, null, flags); - } - else - { - sock = cast(SocketType).accept(handle_, null, null); - } - - if (sock == SocketType.init) - { - if (wouldHaveBlocked()) - { - return null; - } - throw make!SocketException(defaultAllocator, - "Unable to accept socket connection"); - } - - auto newSocket = defaultAllocator.make!ConnectedSocket(sock, addressFamily); - - version (linux) - { // Blocking mode already set - } - else version (Posix) - { - if (!blocking) - { - try - { - newSocket.blocking = blocking; - } - catch (SocketException e) - { - defaultAllocator.dispose(newSocket); - throw e; - } - } - } - else version (Windows) - { // Inherits blocking mode - newSocket.blocking_ = blocking; - } - return newSocket; - } -} - -/** - * Socket returned if a connection has been established. - */ -class ConnectedSocket : Socket, ConnectionOrientedSocket -{ - /** - * $(D_KEYWORD true) if the stream socket peer has performed an orderly - * shutdown. - */ - protected bool disconnected_; - - /** - * Returns: $(D_KEYWORD true) if the stream socket peer has performed an orderly - * shutdown. - */ - @property inout(bool) disconnected() inout const pure nothrow @safe @nogc - { - return disconnected_; - } - - /** - * Create a socket. - * - * Params: - * handle = Socket. - * af = Address family. - */ - this(SocketType handle, AddressFamily af) @nogc - { - super(handle, af); - } - - version (Windows) - { - private static int capToMaxBuffer(size_t size) pure nothrow @safe @nogc - { - // Windows uses int instead of size_t for length arguments. - // Luckily, the send/recv functions make no guarantee that - // all the data is sent, so we use that to send at most - // int.max bytes. - return size > size_t (int.max) ? int.max : cast(int) size; - } - } - else - { - private static size_t capToMaxBuffer(size_t size) pure nothrow @safe @nogc - { - return size; - } - } - - /** - * Receive data on the connection. - * - * Params: - * buf = Buffer to save received data. - * flags = Flags. - * - * Returns: The number of bytes received or 0 if nothing received - * because the call would block. - * - * Throws: $(D_PSYMBOL SocketException) if unable to receive. - */ - ptrdiff_t receive(ubyte[] buf, Flags flags = Flag.none) @trusted @nogc - { - ptrdiff_t ret; - if (!buf.length) - { - return 0; - } - - ret = recv(handle_, buf.ptr, capToMaxBuffer(buf.length), cast(int) flags); - if (ret == 0) - { - disconnected_ = true; - } - else if (ret == socketError) - { - if (wouldHaveBlocked()) - { - return 0; - } - disconnected_ = true; - throw defaultAllocator.make!SocketException("Unable to receive"); - } - return ret; - } - - /** - * Send data on the connection. If the socket is blocking and there is no - * buffer space left, $(D_PSYMBOL send) waits, non-blocking socket returns - * 0 in this case. - * - * Params: - * buf = Data to be sent. - * flags = Flags. - * - * Returns: The number of bytes actually sent. - * - * Throws: $(D_PSYMBOL SocketException) if unable to send. - */ - ptrdiff_t send(const(ubyte)[] buf, Flags flags = Flag.none) - const @trusted @nogc - { - int sendFlags = cast(int) flags; - ptrdiff_t sent; - - static if (is(typeof(MSG_NOSIGNAL))) - { - sendFlags |= MSG_NOSIGNAL; - } - - sent = .send(handle_, buf.ptr, capToMaxBuffer(buf.length), sendFlags); - if (sent != socketError) - { - return sent; - } - else if (wouldHaveBlocked()) - { - return 0; - } - throw defaultAllocator.make!SocketException("Unable to send"); - } -} - -/** - * Socket address representation. - */ -abstract class Address -{ - /** - * Returns: Pointer to underlying $(D_PSYMBOL sockaddr) structure. - */ - abstract @property inout(sockaddr)* name() inout pure nothrow @nogc; - - /** - * Returns: Actual size of underlying $(D_PSYMBOL sockaddr) structure. - */ - abstract @property inout(socklen_t) length() inout const pure nothrow @nogc; -} - -class InternetAddress : Address -{ - version (Windows) - { - /// Internal internet address representation. - protected SOCKADDR_STORAGE storage; - } - else version (Posix) - { - /// Internal internet address representation. - protected sockaddr_storage storage; - } - const ushort port_; - - enum ushort anyPort = 0; - - this(string host, const ushort port = anyPort) @nogc - { - if (getaddrinfoPointer is null || freeaddrinfoPointer is null) - { - throw make!SocketException(defaultAllocator, - "Address info lookup is not available on this system"); - } - addrinfo* ai_res; - this.port_ = port; - - // Make C-string from host. - auto node = cast(char[]) allocator.allocate(host.length + 1); - node[0 .. $ - 1] = host; - node[$ - 1] = '\0'; - scope (exit) - { - allocator.deallocate(node); - } - - // Convert port to a C-string. - char[6] service = [0, 0, 0, 0, 0, 0]; - const(char)* servicePointer; - if (port) - { - ushort originalPort = port; - ushort start; - for (ushort j = 10, i = 4; i > 0; j *= 10, --i) - { - ushort rest = originalPort % 10; - if (rest != 0) - { - service[i] = cast(char) (rest + '0'); - start = i; - } - originalPort /= 10; - } - servicePointer = service[start .. $].ptr; - } - - auto ret = getaddrinfoPointer(node.ptr, servicePointer, null, &ai_res); - if (ret) - { - throw defaultAllocator.make!SocketException("Address info lookup failed"); - } - scope (exit) - { - freeaddrinfoPointer(ai_res); - } - - ubyte* dp = cast(ubyte*) &storage, sp = cast(ubyte*) ai_res.ai_addr; - for (auto i = ai_res.ai_addrlen; i > 0; --i, *dp++, *sp++) - { - *dp = *sp; - } - if (ai_res.ai_family != AddressFamily.inet && ai_res.ai_family != AddressFamily.inet6) - { - throw defaultAllocator.make!SocketException("Wrong address family"); - } - } - - /// - unittest - { - auto address = defaultAllocator.make!InternetAddress("127.0.0.1"); - assert(address.port == InternetAddress.anyPort); - assert(address.name !is null); - assert(address.family == AddressFamily.inet); - - defaultAllocator.dispose(address); - } - - /** - * Returns: Pointer to underlying $(D_PSYMBOL sockaddr) structure. - */ - override @property inout(sockaddr)* name() inout pure nothrow @nogc - { - return cast(sockaddr*) &storage; - } - - /** - * Returns: Actual size of underlying $(D_PSYMBOL sockaddr) structure. - */ - override @property inout(socklen_t) length() inout const pure nothrow @nogc - { - // FreeBSD wants to know the exact length of the address on bind. - switch (family) - { - case AddressFamily.inet: - return sockaddr_in.sizeof; - case AddressFamily.inet6: - return sockaddr_in6.sizeof; - default: - assert(false); - } - } - - /** - * Returns: Family of this address. - */ - @property inout(AddressFamily) family() inout const pure nothrow @nogc - { - return cast(AddressFamily) storage.ss_family; - } - - @property inout(ushort) port() inout const pure nothrow @nogc - { - return port_; - } - - /// - unittest - { - auto address = defaultAllocator.make!InternetAddress("127.0.0.1", - cast(ushort) 1234); - assert(address.port == 1234); - defaultAllocator.dispose(address); - } -} - -/** - * Checks if the last error is a serious error or just a special - * behaviour error of non-blocking sockets (for example an error - * returned because the socket would block or because the - * asynchronous operation was successfully started but not finished yet). - * - * Returns: $(D_KEYWORD false) if a serious error happened, $(D_KEYWORD true) - * otherwise. - */ -bool wouldHaveBlocked() nothrow @trusted @nogc -{ - version (Posix) - { - return errno == EAGAIN || errno == EWOULDBLOCK; - } - else version (Windows) - { - return WSAGetLastError() == ERROR_IO_PENDING - || WSAGetLastError() == WSAEWOULDBLOCK - || WSAGetLastError() == ERROR_IO_INCOMPLETE; - } -} - -/** - * Returns: Platform specific error code. - */ -private @property int lastError() nothrow @safe @nogc -{ - version (Windows) - { - return WSAGetLastError(); - } - else - { - return errno; - } -} -- cgit v1.2.3