diff --git a/qkernel/src/syscalls/sys_poll.rs b/qkernel/src/syscalls/sys_poll.rs index 42aa76950..4cbbee0b8 100644 --- a/qkernel/src/syscalls/sys_poll.rs +++ b/qkernel/src/syscalls/sys_poll.rs @@ -205,7 +205,7 @@ pub fn DoSelect( if (events & SELECT_EXCEPT_EVENTS) != 0 { bitSetCount += 1; } else { - w[i] &= !m; + e[i] &= !m; } } } diff --git a/qlib/kernel/socket/hostinet/uring_socket.rs b/qlib/kernel/socket/hostinet/uring_socket.rs index 265117e26..d6804dafc 100644 --- a/qlib/kernel/socket/hostinet/uring_socket.rs +++ b/qlib/kernel/socket/hostinet/uring_socket.rs @@ -238,6 +238,64 @@ impl UringSocketOperations { return self.connErrNo.load(Ordering::Acquire); } + pub fn SetRemoteAddrFromHost(&self) -> Result<()> { + let mut addr: Vec = + Vec::with_capacity_in(SIZEOF_SOCKADDR, GUEST_HOST_SHARED_ALLOCATOR); + addr.resize(SIZEOF_SOCKADDR, 0); + + let mut len = Box::new_in(SIZEOF_SOCKADDR as i32, GUEST_HOST_SHARED_ALLOCATOR); + let res = Kernel::HostSpace::GetPeerName( + self.fd, + &mut addr[0] as *mut _ as u64, + &mut *len as *mut _ as u64, + ); + if res < 0 { + return Err(Error::SysError(-res as i32)); + } + + let addr = GetAddr(addr[0] as i16, &addr[..*len as usize])?; + *self.remoteAddr.lock() = Some(addr); + return Ok(()); + } + + pub fn CompletePendingConnect(&self) -> Result { + if self.ConnErrno() != -SysErr::EINPROGRESS { + return Ok(self.ConnErrno()); + } + + match self.SocketType() { + UringSocketType::TCPConnecting => (), + _ => return Ok(self.ConnErrno()), + } + + let mut hostErrno = Box::new_in(0i32, GUEST_HOST_SHARED_ALLOCATOR); + let mut hostLen = Box::new_in(4i32, GUEST_HOST_SHARED_ALLOCATOR); + let res = Kernel::HostSpace::GetSockOpt( + self.fd, + LibcConst::SOL_SOCKET as i32, + LibcConst::SO_ERROR as i32, + &mut *hostErrno as *mut i32 as u64, + &mut *hostLen as *mut i32 as u64, + ); + if res < 0 { + return Err(Error::SysError(-res as i32)); + } + + if *hostErrno == 0 { + self.SetRemoteAddrFromHost()?; + self.SetConnErrno(0); + self.PostConnect(); + return Ok(0); + } + + if *hostErrno != SysErr::EINPROGRESS { + self.SetConnErrno(-*hostErrno); + *self.socketType.lock() = UringSocketType::TCPInit; + } + + return Ok(self.ConnErrno()); + } + pub fn New( family: i32, fd: i32, @@ -406,7 +464,15 @@ impl UringSocketOperations { SocketBuffIntern::Init(MemoryDef::DEFAULT_BUF_PAGE_COUNT), GUEST_HOST_SHARED_ALLOCATOR, )); - *self.socketType.lock() = UringSocketType::Uring(socketBuf.clone()); + + { + let mut socketType = self.socketType.lock(); + match &*socketType { + UringSocketType::Uring(_) => return, + _ => *socketType = UringSocketType::Uring(socketBuf.clone()), + } + } + QUring::BufSockInit(self.fd, self.queue.clone(), socketBuf, true).unwrap(); } @@ -500,7 +566,10 @@ impl Waitable for UringSocketOperations { fn Readiness(&self, _task: &Task, mask: EventMask) -> EventMask { match self.SocketType() { UringSocketType::TCPConnecting => { - let errno = self.ConnErrno(); + let errno = match self.CompletePendingConnect() { + Ok(errno) => errno, + Err(_) => self.ConnErrno(), + }; if errno != -SysErr::EINPROGRESS { return EVENT_OUT & mask; } @@ -841,6 +910,10 @@ impl SockOperations for UringSocketOperations { } let errno = self.ConnErrno(); + if errno == 0 { + return Ok(0); + } + return Err(Error::SysError(-errno)); } @@ -1148,6 +1221,8 @@ impl SockOperations for UringSocketOperations { return Err(Error::SysError(SysErr::EINVAL)); } + self.CompletePendingConnect()?; + if self.ConnErrno() != 0 { let errno = self.ConnErrno(); self.SetConnErrno(0); diff --git a/test/c/makefile b/test/c/makefile index a43bba1de..51d9e7dd3 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 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 select_except_connect soerror_connect_loop udpcli udpsrv udpclidual udpsrvdual std: std.c gcc -o std std.c @@ -6,6 +6,10 @@ dnstest: dnstest.c gcc -o dnstest dnstest.c poll: poll.c gcc -o poll poll.c +select_except_connect: select_except_connect.c + gcc -Wall -Wextra -o select_except_connect select_except_connect.c +soerror_connect_loop: soerror_connect_loop.c + gcc -Wall -Wextra -o soerror_connect_loop soerror_connect_loop.c server: server.c gcc -o server server.c client: client.c @@ -79,4 +83,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 select_except_connect soerror_connect_loop unixcli unixsrv socketpair stat dev fork signal futex multithread epoll mkdir fifo timerfd eventfd seek gettimeofday diff --git a/test/c/select_except_connect.c b/test/c/select_except_connect.c new file mode 100644 index 000000000..93676ef69 --- /dev/null +++ b/test/c/select_except_connect.c @@ -0,0 +1,141 @@ +// Copyright (c) 2026 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. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void fail(const char *msg) +{ + perror(msg); + exit(1); +} + +int main(void) +{ + int listener = socket(AF_INET, SOCK_STREAM, 0); + if (listener < 0) { + fail("socket(listener)"); + } + + int one = 1; + if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) { + fail("setsockopt(SO_REUSEADDR)"); + } + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = 0; + + if (bind(listener, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + fail("bind(listener)"); + } + if (listen(listener, 1) < 0) { + fail("listen(listener)"); + } + + socklen_t addr_len = sizeof(addr); + if (getsockname(listener, (struct sockaddr *)&addr, &addr_len) < 0) { + fail("getsockname(listener)"); + } + + pid_t child = fork(); + if (child < 0) { + fail("fork"); + } + if (child == 0) { + int accepted = accept(listener, NULL, NULL); + if (accepted < 0) { + fail("accept"); + } + char byte; + (void)read(accepted, &byte, sizeof(byte)); + close(accepted); + close(listener); + return 0; + } + + int client = socket(AF_INET, SOCK_STREAM, 0); + if (client < 0) { + fail("socket(client)"); + } + int flags = fcntl(client, F_GETFL, 0); + if (flags < 0 || fcntl(client, F_SETFL, flags | O_NONBLOCK) < 0) { + fail("fcntl(O_NONBLOCK)"); + } + + int rc = connect(client, (struct sockaddr *)&addr, sizeof(addr)); + if (rc < 0 && errno != EINPROGRESS) { + fail("connect(client)"); + } + + fd_set readfds; + fd_set writefds; + fd_set exceptfds; + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&exceptfds); + FD_SET(client, &readfds); + FD_SET(client, &writefds); + FD_SET(client, &exceptfds); + + struct timeval timeout; + timeout.tv_sec = 5; + timeout.tv_usec = 0; + rc = select(client + 1, &readfds, &writefds, &exceptfds, &timeout); + if (rc < 0) { + fail("select"); + } + if (rc == 0) { + fprintf(stderr, "select timed out\n"); + return 1; + } + + int err = 0; + socklen_t err_len = sizeof(err); + if (getsockopt(client, SOL_SOCKET, SO_ERROR, &err, &err_len) < 0) { + fail("getsockopt(SO_ERROR)"); + } + + printf("select rc=%d read=%d write=%d except=%d so_error=%d\n", + rc, + FD_ISSET(client, &readfds) != 0, + FD_ISSET(client, &writefds) != 0, + FD_ISSET(client, &exceptfds) != 0, + err); + + if (err != 0) { + fprintf(stderr, "SO_ERROR=%d (%s)\n", err, strerror(err)); + return 1; + } + if (!FD_ISSET(client, &writefds)) { + fprintf(stderr, "client socket was not in writefds\n"); + return 1; + } + if (FD_ISSET(client, &exceptfds)) { + fprintf(stderr, "client socket was incorrectly left in exceptfds\n"); + return 1; + } + + (void)write(client, "x", 1); + close(client); + close(listener); + + int status = 0; + if (waitpid(child, &status, 0) < 0) { + fail("waitpid"); + } + return status == 0 ? 0 : 1; +} diff --git a/test/c/soerror_connect_loop.c b/test/c/soerror_connect_loop.c new file mode 100644 index 000000000..3d1be2dcb --- /dev/null +++ b/test/c/soerror_connect_loop.c @@ -0,0 +1,270 @@ +// Copyright (c) 2026 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. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void fail(const char *msg) +{ + perror(msg); + exit(1); +} + +static int check_refused_connect(void) +{ + int listener = socket(AF_INET, SOCK_STREAM, 0); + if (listener < 0) { + fail("socket(refused listener)"); + } + + int one = 1; + if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) { + fail("setsockopt(refused SO_REUSEADDR)"); + } + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = 0; + + if (bind(listener, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + fail("bind(refused listener)"); + } + + socklen_t addr_len = sizeof(addr); + if (getsockname(listener, (struct sockaddr *)&addr, &addr_len) < 0) { + fail("getsockname(refused listener)"); + } + close(listener); + + int client = socket(AF_INET, SOCK_STREAM, 0); + if (client < 0) { + fail("socket(refused client)"); + } + + int flags = fcntl(client, F_GETFL, 0); + if (flags < 0 || fcntl(client, F_SETFL, flags | O_NONBLOCK) < 0) { + fail("fcntl(refused O_NONBLOCK)"); + } + + int connect_rc = connect(client, (struct sockaddr *)&addr, sizeof(addr)); + if (connect_rc == 0) { + fprintf(stderr, "refused connect unexpectedly succeeded\n"); + close(client); + return 1; + } + if (errno != EINPROGRESS && errno != ECONNREFUSED) { + fail("connect(refused client)"); + } + + fd_set writefds; + FD_ZERO(&writefds); + FD_SET(client, &writefds); + + struct timeval timeout; + timeout.tv_sec = 5; + timeout.tv_usec = 0; + int select_rc = select(client + 1, NULL, &writefds, NULL, &timeout); + if (select_rc < 0) { + fail("select(refused)"); + } + if (select_rc == 0) { + fprintf(stderr, "refused connect select timed out\n"); + close(client); + return 1; + } + + int err = 0; + socklen_t err_len = sizeof(err); + if (getsockopt(client, SOL_SOCKET, SO_ERROR, &err, &err_len) < 0) { + fail("getsockopt(refused SO_ERROR)"); + } + if (err != ECONNREFUSED) { + fprintf(stderr, "expected ECONNREFUSED, got %d (%s)\n", err, strerror(err)); + close(client); + return 1; + } + + int cleared = -1; + err_len = sizeof(cleared); + if (getsockopt(client, SOL_SOCKET, SO_ERROR, &cleared, &err_len) < 0) { + fail("getsockopt(refused SO_ERROR clear)"); + } + if (cleared != 0) { + fprintf(stderr, "SO_ERROR did not clear after read: %d\n", cleared); + close(client); + return 1; + } + + close(client); + return 0; +} + +int main(int argc, char **argv) +{ + int iters = argc > 1 ? atoi(argv[1]) : 100; + int listener = socket(AF_INET, SOCK_STREAM, 0); + if (listener < 0) { + fail("socket(listener)"); + } + + int one = 1; + if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) { + fail("setsockopt(SO_REUSEADDR)"); + } + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = 0; + + if (bind(listener, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + fail("bind(listener)"); + } + if (listen(listener, 128) < 0) { + fail("listen(listener)"); + } + + socklen_t addr_len = sizeof(addr); + if (getsockname(listener, (struct sockaddr *)&addr, &addr_len) < 0) { + fail("getsockname(listener)"); + } + + pid_t child = fork(); + if (child < 0) { + fail("fork"); + } + if (child == 0) { + for (int i = 0; i < iters; i++) { + int accepted = accept(listener, NULL, NULL); + if (accepted >= 0) { + close(accepted); + } + } + close(listener); + return 0; + } + + int bad_immediate = 0; + int bad_except = 0; + int bad_write = 0; + int bad_after = 0; + int bad_peer = 0; + + for (int i = 1; i <= iters; i++) { + int client = socket(AF_INET, SOCK_STREAM, 0); + if (client < 0) { + fail("socket(client)"); + } + + int flags = fcntl(client, F_GETFL, 0); + if (flags < 0 || fcntl(client, F_SETFL, flags | O_NONBLOCK) < 0) { + fail("fcntl(O_NONBLOCK)"); + } + + int connect_rc = connect(client, (struct sockaddr *)&addr, sizeof(addr)); + if (connect_rc < 0 && errno != EINPROGRESS) { + fail("connect(client)"); + } + + int immediate = 0; + socklen_t err_len = sizeof(immediate); + if (getsockopt(client, SOL_SOCKET, SO_ERROR, &immediate, &err_len) < 0) { + fail("getsockopt(SO_ERROR immediate)"); + } + + fd_set readfds; + fd_set writefds; + fd_set exceptfds; + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&exceptfds); + FD_SET(client, &readfds); + FD_SET(client, &writefds); + FD_SET(client, &exceptfds); + + struct timeval timeout; + timeout.tv_sec = 5; + timeout.tv_usec = 0; + int select_rc = select(client + 1, &readfds, &writefds, &exceptfds, &timeout); + if (select_rc < 0) { + fail("select"); + } + + int after = 0; + err_len = sizeof(after); + if (getsockopt(client, SOL_SOCKET, SO_ERROR, &after, &err_len) < 0) { + fail("getsockopt(SO_ERROR after)"); + } + + struct sockaddr_in peer; + memset(&peer, 0, sizeof(peer)); + socklen_t peer_len = sizeof(peer); + int peer_ok = getpeername(client, (struct sockaddr *)&peer, &peer_len) == 0 && + peer.sin_family == AF_INET && + peer.sin_addr.s_addr == addr.sin_addr.s_addr && + peer.sin_port == addr.sin_port; + + int read_ready = FD_ISSET(client, &readfds) != 0; + int write_ready = FD_ISSET(client, &writefds) != 0; + int except_ready = FD_ISSET(client, &exceptfds) != 0; + int bad = immediate != 0 || except_ready || !write_ready || after != 0 || !peer_ok; + if (bad && bad_immediate + bad_except + bad_write + bad_after + bad_peer < 10) { + printf( + "iter=%d connect_rc=%d immediate=%d select_rc=%d read=%d write=%d except=%d after=%d peer_ok=%d\n", + i, + connect_rc, + immediate, + select_rc, + read_ready, + write_ready, + except_ready, + after, + peer_ok); + } + + bad_immediate += immediate != 0; + bad_except += except_ready; + bad_write += !write_ready; + bad_after += after != 0; + bad_peer += !peer_ok; + close(client); + } + + close(listener); + int status = 0; + waitpid(child, &status, 0); + int refused_status = check_refused_connect(); + printf( + "summary bad_immediate=%d/%d bad_except=%d/%d bad_missing_write=%d/%d bad_after=%d/%d bad_peer=%d/%d child_status=%d refused_status=%d\n", + bad_immediate, + iters, + bad_except, + iters, + bad_write, + iters, + bad_after, + iters, + bad_peer, + iters, + status, + refused_status); + + return bad_immediate == 0 && bad_except == 0 && bad_write == 0 && bad_after == 0 && + bad_peer == 0 && status == 0 && refused_status == 0 + ? 0 + : 1; +}