Skip to content

Commit

Permalink
Net: Add SO_LINGER support
Browse files Browse the repository at this point in the history
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 a RST, discarding any unsent data.
  • Loading branch information
logkos committed Sep 27, 2024
1 parent c03be8e commit 0892863
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 1 deletion.
13 changes: 13 additions & 0 deletions Kernel/Net/Socket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ ErrorOr<void> Socket::setsockopt(int level, int option, Userspace<void const*> u
m_broadcast_allowed = TRY(copy_typed_from_user(static_ptr_cast<int const*>(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<linger const*>(user_value)));
return {};
}
default:
dbgln("setsockopt({}) at SOL_SOCKET not implemented.", option);
return ENOPROTOOPT;
Expand Down Expand Up @@ -257,6 +263,13 @@ ErrorOr<void> 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<linger*>(value), &m_linger));
size = sizeof(linger);
return copy_to_user(value_size, &size);
}
default:
dbgln("getsockopt({}) at SOL_SOCKET not implemented.", option);
return ENOPROTOOPT;
Expand Down
1 change: 1 addition & 0 deletions Kernel/Net/Socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
25 changes: 24 additions & 1 deletion Kernel/Net/TCPSocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -664,8 +664,31 @@ ErrorOr<void> 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);
}
Expand Down

0 comments on commit 0892863

Please sign in to comment.