From 175d986c8a24c779f8d8609796d757772d285e70 Mon Sep 17 00:00:00 2001 From: logkos <65683493+logkos@users.noreply.github.com.> Date: Fri, 27 Sep 2024 23:46:03 +0300 Subject: [PATCH] Net: Implement SO_LINGER SO_LINGER allows specifying a timeout interval during which the socket attempts to send any remaining data before closing. If all data is ACKed within the specified timeout, the connection is closed gracefully using a FIN/ACK exchange. If the timeout expires with unacknowledged data, the socket is forcefully closed using RST, discarding any unsent data. --- Kernel/Net/Socket.cpp | 13 +++++++++++++ Kernel/Net/Socket.h | 1 + Kernel/Net/TCPSocket.cpp | 25 ++++++++++++++++++++++++- 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/Kernel/Net/Socket.cpp b/Kernel/Net/Socket.cpp index b8d9df6f09b45f1..53ea97584b7c1e6 100644 --- a/Kernel/Net/Socket.cpp +++ b/Kernel/Net/Socket.cpp @@ -136,6 +136,12 @@ ErrorOr Socket::setsockopt(int level, int option, Userspace u m_broadcast_allowed = TRY(copy_typed_from_user(static_ptr_cast(user_value))) != 0; return {}; } + case SO_LINGER: { + if (user_value_size != sizeof(linger)) + return EINVAL; + m_linger = TRY(copy_typed_from_user(static_ptr_cast(user_value))); + return {}; + } default: dbgln("setsockopt({}) at SOL_SOCKET not implemented.", option); return ENOPROTOOPT; @@ -257,6 +263,13 @@ ErrorOr Socket::getsockopt(OpenFileDescription&, int level, int option, Us size = sizeof(broadcast_allowed); return copy_to_user(value_size, &size); } + case SO_LINGER: { + if (size < sizeof(linger)) + return EINVAL; + TRY(copy_to_user(static_ptr_cast(value), &m_linger)); + size = sizeof(linger); + return copy_to_user(value_size, &size); + } default: dbgln("getsockopt({}) at SOL_SOCKET not implemented.", option); return ENOPROTOOPT; diff --git a/Kernel/Net/Socket.h b/Kernel/Net/Socket.h index 01c154c221a2202..a9511445d7b9bd6 100644 --- a/Kernel/Net/Socket.h +++ b/Kernel/Net/Socket.h @@ -157,6 +157,7 @@ class Socket : public File { ucred m_acceptor { 0, 0, 0 }; bool m_routing_disabled { false }; bool m_broadcast_allowed { false }; + linger m_linger { 0, 0 }; private: virtual bool is_socket() const final { return true; } diff --git a/Kernel/Net/TCPSocket.cpp b/Kernel/Net/TCPSocket.cpp index 4414295b7e8ecb1..dbcceae6e1a66c7 100644 --- a/Kernel/Net/TCPSocket.cpp +++ b/Kernel/Net/TCPSocket.cpp @@ -664,8 +664,31 @@ ErrorOr TCPSocket::close() { MutexLocker locker(mutex()); auto result = IPv4Socket::close(); + if (m_linger.l_onoff != 0) { + if (m_linger.l_linger > 0) { + dbgln_if(TCP_SOCKET_DEBUG, "TCPSocket({}) SO_LINGER with {} seconds interval", this, m_linger.l_linger); + auto deadline = TimeManagement::the().current_time(CLOCK_MONOTONIC_COARSE) + Duration::from_seconds(m_linger.l_linger); + // FIXME: Make this return earlier if all data is acknowledged before end of interval + (void)TimerQueue::the().add_timer_without_id(*m_timer, CLOCK_MONOTONIC_COARSE, deadline, [&]() { + bool has_unacked_data = m_unacked_packets.with_shared([&](auto const& packets) { return packets.size > 0; }); + if (has_unacked_data) { + dbgln_if(TCP_SOCKET_DEBUG, "TCPSocket({}) SO_LINGER interval expired. Some data couldn't make it :(", this); + drop_receive_buffer(); + (void)send_tcp_packet(TCPFlags::RST); + do_state_closed(); + return; + } + dbgln_if(TCP_SOCKET_DEBUG, "TCPSocket({}) SO_LINGER timeout. All remaining data has been acknowledged, closing gracefully", this); + }); + } else { + dbgln_if(TCP_SOCKET_DEBUG, "TCPSocket({}) SO_LINGER without timeout, closing immediately", this); + (void)send_tcp_packet(TCPFlags::RST); + do_state_closed(); + } + } + if (state() == State::CloseWait) { - dbgln_if(TCP_SOCKET_DEBUG, " Sending FIN from CloseWait and moving into LastAck"); + dbgln_if(TCP_SOCKET_DEBUG, "TCPSocket({}) Sending FIN from CloseWait and moving into LastAck", this); [[maybe_unused]] auto rc = send_tcp_packet(TCPFlags::FIN | TCPFlags::ACK); set_state(State::LastAck); }