From 562663df7c13d27834adacaa1fa5cb9a4ef47c2e Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Wed, 13 Mar 2019 13:13:23 +0100 Subject: [PATCH] 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. --- Kernel/IPv4Socket.cpp | 11 +++++--- Kernel/Process.cpp | 54 +++++++++++++++++++++++++++++++++++--- Kernel/Process.h | 4 +++ Kernel/Scheduler.cpp | 12 ++++++--- Kernel/Socket.cpp | 60 +++++++++++++++++++++++++++++++++++++++++++ Kernel/Socket.h | 15 +++++++++++ Kernel/Syscall.cpp | 4 +++ Kernel/Syscall.h | 18 +++++++++++++ Kernel/UnixTypes.h | 5 ++++ LibC/sys/socket.cpp | 14 ++++++++++ LibC/sys/socket.h | 7 +++++ Userland/ping.cpp | 20 +++++++++++++-- 12 files changed, 212 insertions(+), 12 deletions(-) diff --git a/Kernel/IPv4Socket.cpp b/Kernel/IPv4Socket.cpp index 9e0cec8882..4945049dba 100644 --- a/Kernel/IPv4Socket.cpp +++ b/Kernel/IPv4Socket.cpp @@ -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 } diff --git a/Kernel/Process.cpp b/Kernel/Process.cpp index f13d17505e..a33327e2b3 100644 --- a/Kernel/Process.cpp +++ b/Kernel/Process.cpp @@ -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) diff --git a/Kernel/Process.h b/Kernel/Process.h index ae39d25398..b380ab5f8d 100644 --- a/Kernel/Process.h +++ b/Kernel/Process.h @@ -46,6 +46,8 @@ struct DisplayInfo { unsigned pitch; }; +void kgettimeofday(timeval&); + class Process : public InlineLinkedListNode, public Weakable { friend class InlineLinkedListNode; 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); diff --git a/Kernel/Scheduler.cpp b/Kernel/Scheduler.cpp index b7b0b89d5c..7a8748c051 100644 --- a/Kernel/Scheduler.cpp +++ b/Kernel/Scheduler.cpp @@ -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; diff --git a/Kernel/Socket.cpp b/Kernel/Socket.cpp index 3db9a1f80d..3998bdbd48 100644 --- a/Kernel/Socket.cpp +++ b/Kernel/Socket.cpp @@ -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; +} diff --git a/Kernel/Socket.h b/Kernel/Socket.h index e83d0aeb9f..96605cec99 100644 --- a/Kernel/Socket.h +++ b/Kernel/Socket.h @@ -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> m_pending; Vector> m_clients; }; diff --git a/Kernel/Syscall.cpp b/Kernel/Syscall.cpp index 1b2059a3dc..b2363fb3ec 100644 --- a/Kernel/Syscall.cpp +++ b/Kernel/Syscall.cpp @@ -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; diff --git a/Kernel/Syscall.h b/Kernel/Syscall.h index b3586034a4..697449af9e 100644 --- a/Kernel/Syscall.h +++ b/Kernel/Syscall.h @@ -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(); diff --git a/Kernel/UnixTypes.h b/Kernel/UnixTypes.h index 66bd1ffe6e..5c54c1a228 100644 --- a/Kernel/UnixTypes.h +++ b/Kernel/UnixTypes.h @@ -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 diff --git a/LibC/sys/socket.cpp b/LibC/sys/socket.cpp index 10b9fb67b6..1b3cf0569a 100644 --- a/LibC/sys/socket.cpp +++ b/LibC/sys/socket.cpp @@ -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, ¶ms); + __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, ¶ms); + __RETURN_WITH_ERRNO(rc, rc, -1); +} + } diff --git a/LibC/sys/socket.h b/LibC/sys/socket.h index 4721205a19..63d99ab466 100644 --- a/LibC/sys/socket.h +++ b/LibC/sys/socket.h @@ -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 diff --git a/Userland/ping.cpp b/Userland/ping.cpp index b39855cab2..73ce7222b8 100644 --- a/Userland/ping.cpp +++ b/Userland/ping.cpp @@ -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; }