From 7d78e55fcc066c35fda61933e634a84b447bc5d5 Mon Sep 17 00:00:00 2001 From: Daniel Albers Date: Fri, 10 Apr 2026 09:53:27 +0200 Subject: [PATCH] fix: Enable TCP keepalive by default Deskflow previously lacked support for TCP keepalive, which meant that dead connections (such as those resulting from a terminated SSH tunnel or an ungraceful network interruption) could remain in the system's connection table indefinitely. This commit adds the setKeepAliveOnSocket method to the IArchNetwork abstraction and implements it for both BSD/Unix and Winsock/Windows backends. Keepalive is now enabled by default in TCPSocket::init, allowing the operating system to eventually detect and clean up stale connections. --- src/lib/arch/IArchNetwork.h | 6 ++++++ src/lib/arch/unix/ArchNetworkBSD.cpp | 11 +++++++++++ src/lib/arch/unix/ArchNetworkBSD.h | 1 + src/lib/arch/win32/ArchNetworkWinsock.cpp | 11 +++++++++++ src/lib/arch/win32/ArchNetworkWinsock.h | 1 + src/lib/net/TCPSocket.cpp | 3 +++ 6 files changed, 33 insertions(+) diff --git a/src/lib/arch/IArchNetwork.h b/src/lib/arch/IArchNetwork.h index 06a073c10..eaa36cae3 100644 --- a/src/lib/arch/IArchNetwork.h +++ b/src/lib/arch/IArchNetwork.h @@ -225,6 +225,12 @@ public: */ virtual bool setNoDelayOnSocket(ArchSocket, bool noDelay) = 0; + //! Turn keep-alive on or off on socket + /*! + Set socket to use keep-alive (true) or not (false). + */ + virtual void setKeepAliveOnSocket(ArchSocket, bool keepAlive) = 0; + //! Turn address reuse on or off on socket /*! Allows the address this socket is bound to to be reused while in the diff --git a/src/lib/arch/unix/ArchNetworkBSD.cpp b/src/lib/arch/unix/ArchNetworkBSD.cpp index 57d102654..2e731021b 100644 --- a/src/lib/arch/unix/ArchNetworkBSD.cpp +++ b/src/lib/arch/unix/ArchNetworkBSD.cpp @@ -419,6 +419,17 @@ bool ArchNetworkBSD::setNoDelayOnSocket(ArchSocket s, bool noDelay) return (oflag != 0); } +void ArchNetworkBSD::setKeepAliveOnSocket(ArchSocket s, bool keepAlive) +{ + if (!s) { + throwError(EINVAL); + } + int opt = keepAlive; + if (setsockopt(s->m_fd, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&opt), sizeof(opt)) == -1) { + throwError(errno); + } +} + bool ArchNetworkBSD::setReuseAddrOnSocket(ArchSocket s, bool reuse) { assert(s != nullptr); diff --git a/src/lib/arch/unix/ArchNetworkBSD.h b/src/lib/arch/unix/ArchNetworkBSD.h index eef579682..c593ec225 100644 --- a/src/lib/arch/unix/ArchNetworkBSD.h +++ b/src/lib/arch/unix/ArchNetworkBSD.h @@ -86,6 +86,7 @@ public: size_t writeSocket(ArchSocket s, const void *buf, size_t len) override; void throwErrorOnSocket(ArchSocket) override; bool setNoDelayOnSocket(ArchSocket, bool noDelay) override; + void setKeepAliveOnSocket(ArchSocket, bool keepAlive) override; bool setReuseAddrOnSocket(ArchSocket, bool reuse) override; ArchNetAddress newAnyAddr(AddressFamily) override; ArchNetAddress copyAddr(ArchNetAddress) override; diff --git a/src/lib/arch/win32/ArchNetworkWinsock.cpp b/src/lib/arch/win32/ArchNetworkWinsock.cpp index 999a2bc78..50c18c7a8 100644 --- a/src/lib/arch/win32/ArchNetworkWinsock.cpp +++ b/src/lib/arch/win32/ArchNetworkWinsock.cpp @@ -605,6 +605,17 @@ bool ArchNetworkWinsock::setNoDelayOnSocket(ArchSocket s, bool noDelay) return (oflag != 0); } +void ArchNetworkWinsock::setKeepAliveOnSocket(ArchSocket s, bool keepAlive) +{ + if (!s) { + throwError(WSAEINVAL); + } + int opt = keepAlive; + if (setsockopt_winsock(s->m_socket, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt)) == SOCKET_ERROR) { + throwError(getsockerror_winsock()); + } +} + bool ArchNetworkWinsock::setReuseAddrOnSocket(ArchSocket s, bool reuse) { LOG_ERR("socket re-use not supported on windows"); diff --git a/src/lib/arch/win32/ArchNetworkWinsock.h b/src/lib/arch/win32/ArchNetworkWinsock.h index 7b36f6b6f..8ebdc6881 100644 --- a/src/lib/arch/win32/ArchNetworkWinsock.h +++ b/src/lib/arch/win32/ArchNetworkWinsock.h @@ -73,6 +73,7 @@ public: size_t writeSocket(ArchSocket s, const void *buf, size_t len) override; void throwErrorOnSocket(ArchSocket) override; bool setNoDelayOnSocket(ArchSocket, bool noDelay) override; + void setKeepAliveOnSocket(ArchSocket, bool keepAlive) override; bool setReuseAddrOnSocket(ArchSocket, bool reuse) override; ArchNetAddress newAnyAddr(AddressFamily) override; ArchNetAddress copyAddr(ArchNetAddress) override; diff --git a/src/lib/net/TCPSocket.cpp b/src/lib/net/TCPSocket.cpp index 8d691a329..b9d70f934 100644 --- a/src/lib/net/TCPSocket.cpp +++ b/src/lib/net/TCPSocket.cpp @@ -283,6 +283,9 @@ void TCPSocket::init() // that should be sent without (much) delay. for example, the // mouse motion messages are much less useful if they're delayed. ARCH->setNoDelayOnSocket(m_socket, true); + + // turn on keep-alive to avoid dead connections from lingering indefinitely + ARCH->setKeepAliveOnSocket(m_socket, true); } catch (const ArchNetworkException &e) { try { ARCH->closeSocket(m_socket);