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.
This commit is contained in:
Daniel Albers 2026-04-10 09:53:27 +02:00 committed by Chris Rizzitello
parent acd02a01e6
commit 7d78e55fcc
6 changed files with 33 additions and 0 deletions

View file

@ -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

View file

@ -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<const optval_t *>(&opt), sizeof(opt)) == -1) {
throwError(errno);
}
}
bool ArchNetworkBSD::setReuseAddrOnSocket(ArchSocket s, bool reuse)
{
assert(s != nullptr);

View file

@ -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;

View file

@ -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");

View file

@ -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;

View file

@ -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);