Add support for socket send/receive timeouts.

Only the receive timeout is hooked up yet. You can change the timeout by
calling setsockopt(..., SOL_SOCKET, SO_RCVTIMEO, ...).

Use this mechanism to make /bin/ping report timeouts.
This commit is contained in:
Andreas Kling 2019-03-13 13:13:23 +01:00
parent 7bcd386338
commit 562663df7c
Notes: sideshowbarker 2024-07-19 15:04:27 +09:00
12 changed files with 212 additions and 12 deletions

View file

@ -157,10 +157,15 @@ ssize_t IPv4Socket::recvfrom(void* buffer, size_t buffer_length, int flags, cons
}
if (packet_buffer.is_null()) {
current->set_blocked_socket(this);
load_receive_deadline();
block(Process::BlockedReceive);
Scheduler::yield();
LOCKER(m_lock);
if (!m_can_read) {
// Unblocked due to timeout.
return -EAGAIN;
}
ASSERT(m_can_read);
ASSERT(!m_receive_queue.is_empty());
packet_buffer = m_receive_queue.take_first();
@ -175,10 +180,10 @@ ssize_t IPv4Socket::recvfrom(void* buffer, size_t buffer_length, int flags, cons
void IPv4Socket::did_receive(ByteBuffer&& packet)
{
#ifdef IPV4_SOCKET_DEBUG
kprintf("IPv4Socket(%p): did_receive %d bytes\n", this, packet.size());
#endif
LOCKER(m_lock);
m_receive_queue.append(move(packet));
m_can_read = true;
#ifdef IPV4_SOCKET_DEBUG
kprintf("IPv4Socket(%p): did_receive %d bytes, packets in queue: %d\n", this, packet.size(), m_receive_queue.size_slow());
#endif
}

View file

@ -1589,13 +1589,17 @@ int Process::sys$sleep(unsigned seconds)
return 0;
}
void kgettimeofday(timeval& tv)
{
tv.tv_sec = RTC::now();
tv.tv_usec = (PIT::ticks_since_boot() % 1000) * 1000;
}
int Process::sys$gettimeofday(timeval* tv)
{
if (!validate_write_typed(tv))
return -EFAULT;
auto now = RTC::now();
tv->tv_sec = now;
tv->tv_usec = (PIT::ticks_since_boot() % 1000) * 1000;
kgettimeofday(*tv);
return 0;
}
@ -2567,6 +2571,50 @@ ssize_t Process::sys$recvfrom(const Syscall::SC_recvfrom_params* params)
return socket.recvfrom(buffer, buffer_length, flags, addr, addr_length);
}
int Process::sys$getsockopt(const Syscall::SC_getsockopt_params* params)
{
if (!validate_read_typed(params))
return -EFAULT;
int sockfd = params->sockfd;
int level = params->level;
int option = params->option;
auto* value = params->value;
auto* value_size = (socklen_t*)params->value_size;
if (!validate_write_typed(value_size))
return -EFAULT;
if (!validate_write(value, *value_size))
return -EFAULT;
auto* descriptor = file_descriptor(sockfd);
if (!descriptor)
return -EBADF;
if (!descriptor->is_socket())
return -ENOTSOCK;
auto& socket = *descriptor->socket();
return socket.getsockopt(level, option, value, value_size);
}
int Process::sys$setsockopt(const Syscall::SC_setsockopt_params* params)
{
if (!validate_read_typed(params))
return -EFAULT;
int sockfd = params->sockfd;
int level = params->level;
int option = params->option;
auto* value = params->value;
auto value_size = (socklen_t)params->value_size;
if (!validate_read(value, value_size))
return -EFAULT;
auto* descriptor = file_descriptor(sockfd);
if (!descriptor)
return -EBADF;
if (!descriptor->is_socket())
return -ENOTSOCK;
auto& socket = *descriptor->socket();
return socket.setsockopt(level, option, value, value_size);
}
struct SharedBuffer {
SharedBuffer(pid_t pid1, pid_t pid2, int size)
: m_pid1(pid1)

View file

@ -46,6 +46,8 @@ struct DisplayInfo {
unsigned pitch;
};
void kgettimeofday(timeval&);
class Process : public InlineLinkedListNode<Process>, public Weakable<Process> {
friend class InlineLinkedListNode<Process>;
public:
@ -233,6 +235,8 @@ public:
int sys$connect(int sockfd, const sockaddr*, socklen_t);
ssize_t sys$sendto(const Syscall::SC_sendto_params*);
ssize_t sys$recvfrom(const Syscall::SC_recvfrom_params*);
int sys$getsockopt(const Syscall::SC_getsockopt_params*);
int sys$setsockopt(const Syscall::SC_setsockopt_params*);
int sys$restore_signal_mask(dword mask);
int sys$create_shared_buffer(pid_t peer_pid, int, void** buffer);

View file

@ -54,8 +54,11 @@ bool Scheduler::pick_next()
return context_switch(*s_colonel_process);
}
auto now_sec = RTC::now();
auto now_usec = (suseconds_t)((PIT::ticks_since_boot() % 1000) * 1000);
// Check and unblock processes whose wait conditions have been met.
Process::for_each([] (Process& process) {
Process::for_each([&] (Process& process) {
if (process.state() == Process::BlockedSleep) {
if (process.wakeup_time() <= system.uptime)
process.unblock();
@ -100,18 +103,19 @@ bool Scheduler::pick_next()
if (process.state() == Process::BlockedReceive) {
ASSERT(process.m_blocked_socket);
auto& socket = *process.m_blocked_socket;
// FIXME: Block until the amount of data wanted is available.
if (process.m_blocked_socket->can_read(SocketRole::None)) {
bool timed_out = now_sec > socket.receive_deadline().tv_sec || (now_sec == socket.receive_deadline().tv_sec && now_usec >= socket.receive_deadline().tv_usec);
if (timed_out || socket.can_read(SocketRole::None)) {
process.unblock();
process.m_blocked_socket = nullptr;
return true;
}
return true;
}
if (process.state() == Process::BlockedSelect) {
if (process.m_select_has_timeout) {
auto now_sec = RTC::now();
auto now_usec = PIT::ticks_since_boot() % 1000;
if (now_sec > process.m_select_timeout.tv_sec || (now_sec == process.m_select_timeout.tv_sec && now_usec >= process.m_select_timeout.tv_usec)) {
process.unblock();
return true;

View file

@ -60,3 +60,63 @@ KResult Socket::queue_connection_from(Socket& peer)
m_pending.append(peer);
return KSuccess;
}
KResult Socket::setsockopt(int level, int option, const void* value, socklen_t value_size)
{
ASSERT(level == SOL_SOCKET);
switch (option) {
case SO_SNDTIMEO:
if (value_size != sizeof(timeval))
return KResult(-EINVAL);
m_send_timeout = *(timeval*)value;
return KSuccess;
case SO_RCVTIMEO:
if (value_size != sizeof(timeval))
return KResult(-EINVAL);
m_receive_timeout = *(timeval*)value;
return KSuccess;
default:
kprintf("%s(%u): setsockopt() at SOL_SOCKET with unimplemented option %d\n", option);
return KResult(-ENOPROTOOPT);
}
}
KResult Socket::getsockopt(int level, int option, void* value, socklen_t* value_size)
{
ASSERT(level == SOL_SOCKET);
switch (option) {
case SO_SNDTIMEO:
if (*value_size < sizeof(timeval))
return KResult(-EINVAL);
*(timeval*)value = m_send_timeout;
*value_size = sizeof(timeval);
return KSuccess;
case SO_RCVTIMEO:
if (*value_size < sizeof(timeval))
return KResult(-EINVAL);
*(timeval*)value = m_receive_timeout;
*value_size = sizeof(timeval);
return KSuccess;
default:
kprintf("%s(%u): getsockopt() at SOL_SOCKET with unimplemented option %d\n", option);
return KResult(-ENOPROTOOPT);
}
}
void Socket::load_receive_deadline()
{
kgettimeofday(m_receive_deadline);
m_receive_deadline.tv_sec += m_receive_timeout.tv_sec;
m_receive_deadline.tv_usec += m_receive_timeout.tv_usec;
m_receive_deadline.tv_sec += (m_send_timeout.tv_usec / 1000000) * 1;
m_receive_deadline.tv_usec %= 1000000;
}
void Socket::load_send_deadline()
{
kgettimeofday(m_send_deadline);
m_send_deadline.tv_sec += m_send_timeout.tv_sec;
m_send_deadline.tv_usec += m_send_timeout.tv_usec;
m_send_deadline.tv_sec += (m_send_timeout.tv_usec / 1000000) * 1;
m_send_deadline.tv_usec %= 1000000;
}

View file

@ -38,13 +38,22 @@ public:
virtual ssize_t sendto(const void*, size_t, int flags, const sockaddr*, socklen_t) = 0;
virtual ssize_t recvfrom(void*, size_t, int flags, const sockaddr*, socklen_t) = 0;
KResult setsockopt(int level, int option, const void*, socklen_t);
KResult getsockopt(int level, int option, void*, socklen_t*);
pid_t origin_pid() const { return m_origin_pid; }
timeval receive_deadline() const { return m_receive_deadline; }
timeval send_deadline() const { return m_send_deadline; }
protected:
Socket(int domain, int type, int protocol);
KResult queue_connection_from(Socket&);
void load_receive_deadline();
void load_send_deadline();
private:
Lock m_lock;
pid_t m_origin_pid { 0 };
@ -54,6 +63,12 @@ private:
int m_backlog { 0 };
bool m_connected { false };
timeval m_receive_timeout { 0, 0 };
timeval m_send_timeout { 0, 0 };
timeval m_receive_deadline { 0, 0 };
timeval m_send_deadline { 0, 0 };
Vector<RetainPtr<Socket>> m_pending;
Vector<RetainPtr<Socket>> m_clients;
};

View file

@ -231,6 +231,10 @@ static dword handle(RegisterDump& regs, dword function, dword arg1, dword arg2,
return current->sys$sendto((const SC_sendto_params*)arg1);
case Syscall::SC_recvfrom:
return current->sys$recvfrom((const SC_recvfrom_params*)arg1);
case Syscall::SC_getsockopt:
return current->sys$getsockopt((const SC_getsockopt_params*)arg1);
case Syscall::SC_setsockopt:
return current->sys$setsockopt((const SC_setsockopt_params*)arg1);
default:
kprintf("<%u> int0x82: Unknown function %u requested {%x, %x, %x}\n", current->pid(), function, arg1, arg2, arg3);
break;

View file

@ -90,6 +90,8 @@
__ENUMERATE_SYSCALL(seal_shared_buffer) \
__ENUMERATE_SYSCALL(sendto) \
__ENUMERATE_SYSCALL(recvfrom) \
__ENUMERATE_SYSCALL(getsockopt) \
__ENUMERATE_SYSCALL(setsockopt) \
namespace Syscall {
@ -148,6 +150,22 @@ struct SC_recvfrom_params {
size_t addr_length; // socklen_t
};
struct SC_getsockopt_params {
int sockfd;
int level;
int option;
void* value;
void* value_size; // socklen_t*
};
struct SC_setsockopt_params {
int sockfd;
int level;
int option;
const void* value;
size_t value_size; // socklen_t
};
void initialize();
int sync();

View file

@ -325,6 +325,11 @@ struct pollfd {
#define SOCK_NONBLOCK 04000
#define SOCK_CLOEXEC 02000000
#define SOL_SOCKET 1
#define SO_RCVTIMEO 1
#define SO_SNDTIMEO 2
#define IPPROTO_ICMP 1
#define IPPROTO_TCP 6
#define IPPROTO_UDP 17

View file

@ -48,4 +48,18 @@ ssize_t recvfrom(int sockfd, void* buffer, size_t buffer_length, int flags, cons
__RETURN_WITH_ERRNO(rc, rc, -1);
}
int getsockopt(int sockfd, int level, int option, void* value, socklen_t* value_size)
{
Syscall::SC_getsockopt_params params { sockfd, level, option, value, value_size };
int rc = syscall(SC_getsockopt, &params);
__RETURN_WITH_ERRNO(rc, rc, -1);
}
int setsockopt(int sockfd, int level, int option, const void* value, socklen_t value_size)
{
Syscall::SC_setsockopt_params params { sockfd, level, option, value, value_size };
int rc = syscall(SC_setsockopt, &params);
__RETURN_WITH_ERRNO(rc, rc, -1);
}
}

View file

@ -45,6 +45,11 @@ struct sockaddr_in {
char sin_zero[8];
};
#define SOL_SOCKET 1
#define SO_RCVTIMEO 1
#define SO_SNDTIMEO 2
int socket(int domain, int type, int protocol);
int bind(int sockfd, const sockaddr* addr, socklen_t);
int listen(int sockfd, int backlog);
@ -52,6 +57,8 @@ int accept(int sockfd, sockaddr*, socklen_t*);
int connect(int sockfd, const sockaddr*, socklen_t);
ssize_t sendto(int sockfd, const void*, size_t, int flags, const struct sockaddr*, socklen_t);
ssize_t recvfrom(int sockfd, void*, size_t, int flags, const struct sockaddr*, socklen_t);
int getsockopt(int sockfd, int level, int option, void*, socklen_t*);
int setsockopt(int sockfd, int level, int option, const void*, socklen_t);
__END_DECLS

View file

@ -39,6 +39,13 @@ int main(int argc, char** argv)
return 1;
}
struct timeval timeout { 1, 0 };
int rc = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
if (rc < 0) {
perror("setsockopt");
return 1;
}
const char* addr_str = "192.168.5.1";
if (argc > 1)
addr_str = argv[1];
@ -50,7 +57,7 @@ int main(int argc, char** argv)
peer_address.sin_family = AF_INET;
peer_address.sin_port = 0;
int rc = inet_pton(AF_INET, addr_str, &peer_address.sin_addr);
rc = inet_pton(AF_INET, addr_str, &peer_address.sin_addr);
struct PingPacket {
struct icmphdr header;
@ -84,6 +91,10 @@ int main(int argc, char** argv)
for (;;) {
rc = recvfrom(fd, &pong_packet, sizeof(PingPacket), 0, (const struct sockaddr*)&peer_address, sizeof(sockaddr_in));
if (rc < 0) {
if (errno == EAGAIN) {
printf("Request (seq=%u) timed out.\n", ntohs(ping_packet.header.un.echo.sequence));
break;
}
perror("recvfrom");
return 1;
}
@ -104,12 +115,17 @@ int main(int argc, char** argv)
int ms = tv_diff.tv_sec * 1000 + tv_diff.tv_usec / 1000;
char addr_buf[64];
printf("Pong from %s: id=%u, seq=%u, time=%dms\n",
printf("Pong from %s: id=%u, seq=%u%s, time=%dms\n",
inet_ntop(AF_INET, &peer_address.sin_addr, addr_buf, sizeof(addr_buf)),
ntohs(pong_packet.header.un.echo.id),
ntohs(pong_packet.header.un.echo.sequence),
pong_packet.header.un.echo.sequence != ping_packet.header.un.echo.sequence ? "(!)" : "",
ms
);
// If this was a response to an earlier packet, we still need to wait for the current one.
if (pong_packet.header.un.echo.sequence != ping_packet.header.un.echo.sequence)
continue;
break;
}