diff --git a/source/eventcore/driver.d b/source/eventcore/driver.d index ddd196b..2c95492 100644 --- a/source/eventcore/driver.d +++ b/source/eventcore/driver.d @@ -215,6 +215,19 @@ interface EventDriverSockets { /// Sets to `SO_KEEPALIVE` socket option on a socket. void setKeepAlive(StreamSocketFD socket, bool enable); + /** Enables keepalive for the TCP socket and sets additional parameters. + Silently ignores unsupported systems. + + Params: + socket = Socket file descriptor to set options on. + idle = The time the connection needs to remain idle + before TCP starts sending keepalive probes. + interval = The time between individual keepalive probes. + probeCount = The maximum number of keepalive probes TCP should send + before dropping the connection. Has no effect on Windows. + */ + void setKeepAliveParams(StreamSocketFD socket, Duration idle, Duration interval, int probeCount = 5); + /** Reads data from a stream socket. Note that only a single read operation is allowed at once. The caller diff --git a/source/eventcore/drivers/posix/sockets.d b/source/eventcore/drivers/posix/sockets.d index 1dc381f..1abeb66 100644 --- a/source/eventcore/drivers/posix/sockets.d +++ b/source/eventcore/drivers/posix/sockets.d @@ -8,6 +8,8 @@ import eventcore.internal.utils; import std.algorithm.comparison : among, min, max; import std.socket : Address, AddressFamily, InternetAddress, Internet6Address, UnknownAddress; +import core.time: Duration; + version (Posix) { import std.socket : UnixAddress; import core.sys.posix.netdb : AI_ADDRCONFIG, AI_V4MAPPED, addrinfo, freeaddrinfo, getaddrinfo; @@ -45,6 +47,13 @@ version (linux) { } else import core.sys.linux.netinet.in_ : IP_ADD_MEMBERSHIP, IP_MULTICAST_LOOP; + + // Linux-specific TCP options + // https://github.com/torvalds/linux/blob/master/include/uapi/linux/tcp.h#L95 + enum SOL_TCP = 6; + enum TCP_KEEPIDLE = 4; + enum TCP_KEEPINTVL = 5; + enum TCP_KEEPCNT = 6; } version(OSX) { static if (__VERSION__ < 2077) { @@ -300,6 +309,19 @@ final class PosixEventDriverSockets(Loop : PosixEventLoop) : EventDriverSockets () @trusted { setsockopt(cast(sock_t)socket, SOL_SOCKET, SO_KEEPALIVE, cast(char*)&opt, opt.sizeof); } (); } + override void setKeepAliveParams(StreamSocketFD socket, Duration idle, Duration interval, int probeCount) @trusted + { + version (linux) { + ubyte opt = 1; + setsockopt(cast(sock_t)socket, SOL_SOCKET, SO_KEEPALIVE, cast(char*)&opt, opt.sizeof); + int int_opt = cast(int) idle.total!"seconds"(); + setsockopt(cast(sock_t)socket, SOL_TCP, TCP_KEEPIDLE, &int_opt, int.sizeof); + int_opt = cast(int) interval.total!"seconds"(); + setsockopt(cast(sock_t)socket, SOL_TCP, TCP_KEEPINTVL, &int_opt, int.sizeof); + setsockopt(cast(sock_t)socket, SOL_TCP, TCP_KEEPCNT, &probeCount, int.sizeof); + } + } + final override void read(StreamSocketFD socket, ubyte[] buffer, IOMode mode, IOCallback on_read_finish) { /*if (buffer.length == 0) { diff --git a/source/eventcore/drivers/winapi/sockets.d b/source/eventcore/drivers/winapi/sockets.d index 7200dd9..8ffeb57 100644 --- a/source/eventcore/drivers/winapi/sockets.d +++ b/source/eventcore/drivers/winapi/sockets.d @@ -221,6 +221,18 @@ final class WinAPIEventDriverSockets : EventDriverSockets { setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, &eni, eni.sizeof); } + override void setKeepAliveParams(StreamSocketFD socket, Duration idle, Duration interval, int probeCount) @trusted + { + if (idle < Duration.zero) + assert(0, "negative idle duration"); + if (interval < Duration.zero) + assert(0, "negative interval duration"); + tcp_keepalive opts = tcp_keepalive(1, cast(ulong) idle.total!"msecs"(), + cast(ulong) interval.total!"msecs"); + int result = WSAIoctl(socket, SIO_KEEPALIVE_VALS, &opts, tcp_keepalive.sizeof, null, 0, null, null); + assert(result == 0); + } + override void read(StreamSocketFD socket, ubyte[] buffer, IOMode mode, IOCallback on_read_finish) { auto slot = () @trusted { return &m_sockets[socket].streamSocket(); } (); diff --git a/source/eventcore/internal/win32.d b/source/eventcore/internal/win32.d index af068b3..69c99a3 100644 --- a/source/eventcore/internal/win32.d +++ b/source/eventcore/internal/win32.d @@ -104,6 +104,29 @@ struct ADDRINFOW { ADDRINFOW* ai_next; } +// https://msdn.microsoft.com/en-us/library/windows/desktop/dd877220(v=vs.85).aspx +struct tcp_keepalive { + ulong onoff; + ulong keepalivetime; + ulong keepaliveinterval; +}; + +// https://gist.github.com/piscisaureus/906386#file-winsock2-h-L1099 +enum : ulong { + IOC_VENDOR = 0x18000000, + IOC_OUT = 0x40000000, + IOC_IN = 0x80000000 +} + +ulong _WSAIOW(ulong x, ulong y) pure @safe +{ + return IOC_IN | x | y; +} + +enum : ulong { + SIO_KEEPALIVE_VALS = _WSAIOW(IOC_VENDOR, 4) +} + struct WSAPROTOCOL_INFO { DWORD dwServiceFlags1; DWORD dwServiceFlags2; @@ -154,6 +177,7 @@ void FreeAddrInfoExW(ADDRINFOEXW* pAddrInfo); void freeaddrinfo(ADDRINFOA* ai); BOOL TransmitFile(SOCKET hSocket, HANDLE hFile, DWORD nNumberOfBytesToWrite, DWORD nNumberOfBytesPerSend, OVERLAPPED* lpOverlapped, LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers, DWORD dwFlags); BOOL CancelIoEx(HANDLE hFile, LPOVERLAPPED lpOverlapped); +int WSAIoctl(SOCKET s, DWORD dwIoControlCode, void* lpvInBuffer, DWORD cbInBuffer, void* lpvOutBuffer, DWORD cbOutBuffer, DWORD* lpcbBytesReturned, in WSAOVERLAPPEDX* lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINEX lpCompletionRoutine); /*struct WSAOVERLAPPEDX { ULONG_PTR Internal;