From 8821622e52b287910f3cdeaa14bb6d091daba960 Mon Sep 17 00:00:00 2001 From: russell romney Date: Thu, 4 Jun 2026 15:56:40 -0700 Subject: [PATCH] hostinet: drain recv buffer before EOF A TCP peer can send data and FIN close enough together that qvisor marks the socket read side closed while bytes are already buffered. RecvMsg returned EOF from an early RClosed check before consulting the receive buffer, so the buffered bytes were never surfaced and retries kept returning EOF. Let the existing Readv path decide the result instead. It already returns buffered data before EOF and only reports EOF once the read side is closed and the buffer is empty. Add a small TCP regression client for the data-plus-FIN case. --- qlib/kernel/socket/hostinet/tsotsocket.rs | 13 --- qlib/kernel/socket/hostinet/uring_socket.rs | 13 --- test/c/makefile | 6 +- test/c/tcp_fin_recv.c | 110 ++++++++++++++++++++ 4 files changed, 114 insertions(+), 28 deletions(-) create mode 100644 test/c/tcp_fin_recv.c diff --git a/qlib/kernel/socket/hostinet/tsotsocket.rs b/qlib/kernel/socket/hostinet/tsotsocket.rs index bef91b2a0..065282d6c 100644 --- a/qlib/kernel/socket/hostinet/tsotsocket.rs +++ b/qlib/kernel/socket/hostinet/tsotsocket.rs @@ -1396,19 +1396,6 @@ impl SockOperations for TsotSocketOperations { let trunc = (flags & MsgType::MSG_TRUNC) != 0; let peek = (flags & MsgType::MSG_PEEK) != 0; - if buf.RClosed() { - let senderAddr = if senderRequested { - let addr = self.RemoteAddr()?.ToSockAddr(); - let l = addr.Len(); - Some((addr, l)) - } else { - None - }; - - let (retFlags, controlData) = self.prepareControlMessage(controlDataLen); - return Ok((0 as i64, retFlags, senderAddr, controlData)); - } - let len = IoVec::NumBytes(dsts); let data = if trunc { Some(Iovs(dsts).Data()) } else { None }; diff --git a/qlib/kernel/socket/hostinet/uring_socket.rs b/qlib/kernel/socket/hostinet/uring_socket.rs index 265117e26..929d06a7e 100644 --- a/qlib/kernel/socket/hostinet/uring_socket.rs +++ b/qlib/kernel/socket/hostinet/uring_socket.rs @@ -1360,19 +1360,6 @@ impl SockOperations for UringSocketOperations { let trunc = (flags & MsgType::MSG_TRUNC) != 0; let peek = (flags & MsgType::MSG_PEEK) != 0; - if buf.RClosed() { - let senderAddr = if senderRequested { - let addr = self.remoteAddr.lock().as_ref().unwrap().clone(); - let l = addr.Len(); - Some((addr, l)) - } else { - None - }; - - let (retFlags, controlData) = self.prepareControlMessage(controlDataLen); - return Ok((0 as i64, retFlags, senderAddr, controlData)); - } - let len = IoVec::NumBytes(dsts); let data = if trunc { Some(Iovs(dsts).Data()) } else { None }; diff --git a/test/c/makefile b/test/c/makefile index a43bba1de..1b7f35f30 100644 --- a/test/c/makefile +++ b/test/c/makefile @@ -1,4 +1,4 @@ -all: std server client server_conn client_conn unixcli unixsrv socketpair stat dev fork signal futex multithread epoll mkdir fifo timerfd eventfd seek gettimeofday server_benchmark client_benchmark epoll_client epoll_server multithread_client multithread_server multithread_pp_client multithread_pp_server poll udpcli udpsrv udpclidual udpsrvdual +all: std server client server_conn client_conn tcp_fin_recv unixcli unixsrv socketpair stat dev fork signal futex multithread epoll mkdir fifo timerfd eventfd seek gettimeofday server_benchmark client_benchmark epoll_client epoll_server multithread_client multithread_server multithread_pp_client multithread_pp_server poll udpcli udpsrv udpclidual udpsrvdual std: std.c gcc -o std std.c @@ -14,6 +14,8 @@ server_conn: server_conn.c gcc -o server_conn server_conn.c client_conn: client_conn.c gcc -o client_conn client_conn.c +tcp_fin_recv: tcp_fin_recv.c + gcc -static -o tcp_fin_recv tcp_fin_recv.c epoll_client: epoll_client.c gcc -o epollc epoll_client.c epoll_server: epoll_server.c @@ -79,4 +81,4 @@ udpclidual: udpclidual.c udpsrvdual: udpsrvdual.c gcc -o udpsrvdual udpsrvdual.c clean: - rm std server client unixcli unixsrv socketpair stat dev fork signal futex multithread epoll mkdir fifo timerfd eventfd seek gettimeofday + rm -f std server client tcp_fin_recv unixcli unixsrv socketpair stat dev fork signal futex multithread epoll mkdir fifo timerfd eventfd seek gettimeofday diff --git a/test/c/tcp_fin_recv.c b/test/c/tcp_fin_recv.c new file mode 100644 index 000000000..a290eb4b3 --- /dev/null +++ b/test/c/tcp_fin_recv.c @@ -0,0 +1,110 @@ +// Copyright (c) 2021 Quark Container Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include + +#define DEFAULT_HOST "127.0.0.1" +#define DEFAULT_PORT 47050 +#define DEFAULT_ITERS 32 + +static int run_once(const char *host, int port, const char *expected) { + int fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + perror("socket"); + return 1; + } + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + if (inet_pton(AF_INET, host, &addr.sin_addr) != 1) { + fprintf(stderr, "invalid IPv4 address: %s\n", host); + close(fd); + return 1; + } + + if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + perror("connect"); + close(fd); + return 1; + } + + if (write(fd, "ping\n", 5) != 5) { + perror("write"); + close(fd); + return 1; + } + + char buf[64]; + ssize_t n = recv(fd, buf, sizeof(buf), 0); + if (n == 0) { + fprintf(stderr, "first recv returned EOF\n"); + close(fd); + return 2; + } + if (n < 0) { + perror("recv"); + close(fd); + return 1; + } + if (expected != NULL && ((size_t)n != strlen(expected) || memcmp(buf, expected, (size_t)n) != 0)) { + fprintf(stderr, "first recv returned unexpected payload length=%zd\n", n); + close(fd); + return 3; + } + + n = recv(fd, buf, sizeof(buf), 0); + if (n != 0) { + if (n < 0) { + perror("second recv"); + } else { + fprintf(stderr, "second recv returned data after expected EOF length=%zd\n", n); + } + close(fd); + return 4; + } + + close(fd); + return 0; +} + +int main(int argc, char **argv) { + const char *host = argc > 1 ? argv[1] : DEFAULT_HOST; + int port = argc > 2 ? atoi(argv[2]) : DEFAULT_PORT; + int iters = argc > 3 ? atoi(argv[3]) : DEFAULT_ITERS; + const char *expected = argc > 4 ? argv[4] : NULL; + + if (port <= 0 || iters <= 0) { + fprintf(stderr, "usage: %s [host] [port] [iterations] [expected-payload]\n", argv[0]); + return 1; + } + + for (int i = 0; i < iters; i++) { + int ret = run_once(host, port, expected); + if (ret != 0) { + fprintf(stderr, "tcp_fin_recv failed at iteration %d\n", i + 1); + return ret; + } + } + + printf("tcp_fin_recv: %d iterations ok\n", iters); + return 0; +}