Skip to content

Commit 010013b

Browse files
ryanbreenclaude
andcommitted
fix(net): add packet polling to networking syscalls for first-call readiness
TCP listeners were not detecting pending connections on the first poll/select/accept call because incoming SYN packets remained unprocessed in the e1000 driver buffer. The fix ensures process_rx() and drain_loopback_queue() are called at syscall entry. Changes: - Add process_rx() + drain_loopback_queue() to sys_poll, sys_select, sys_accept, sys_recvfrom, and sys_read for TcpConnection - Add first-call accept test (Test 25) without retry loop - Add TCP listener poll test (Phase 8) verifying POLLIN on first call - Add TCP listener select test (Phase 8) verifying readiness on first call - Fix PTY master reference counting for fork/exec scenarios - Add telnetd to ext2 filesystem and update run.sh for interactive mode All 226 boot stages pass. Tests verify first-call success without retry loops, ensuring the fix actually works rather than masking timing issues. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 5216c26 commit 010013b

File tree

11 files changed

+416
-23
lines changed

11 files changed

+416
-23
lines changed

kernel/src/ipc/fd.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,12 +205,20 @@ impl Clone for FdTable {
205205

206206
let cloned_fds = alloc::boxed::Box::new((*self.fds).clone());
207207

208-
// Increment pipe reference counts for all cloned pipe fds
208+
// Increment reference counts for all cloned fds that need it
209209
for fd_opt in cloned_fds.iter() {
210210
if let Some(fd_entry) = fd_opt {
211211
match &fd_entry.kind {
212212
FdKind::PipeRead(buffer) => buffer.lock().add_reader(),
213213
FdKind::PipeWrite(buffer) => buffer.lock().add_writer(),
214+
FdKind::PtyMaster(pty_num) => {
215+
// Increment PTY master reference count for the clone
216+
if let Some(pair) = crate::tty::pty::get(*pty_num) {
217+
let old_count = pair.master_refcount.fetch_add(1, core::sync::atomic::Ordering::SeqCst);
218+
log::debug!("FdTable::clone() - PTY master {} refcount {} -> {}",
219+
pty_num, old_count, old_count + 1);
220+
}
221+
}
214222
_ => {}
215223
}
216224
}
@@ -470,9 +478,16 @@ impl Drop for FdTable {
470478
log::debug!("FdTable::drop() - releasing devpts directory fd {}", i);
471479
}
472480
FdKind::PtyMaster(pty_num) => {
473-
// PTY master cleanup - release the PTY pair when master closes
474-
crate::tty::pty::release(pty_num);
475-
log::debug!("FdTable::drop() - released PTY master fd {} (pty {})", i, pty_num);
481+
// PTY master cleanup - decrement refcount, only release when all masters closed
482+
if let Some(pair) = crate::tty::pty::get(pty_num) {
483+
let old_count = pair.master_refcount.fetch_sub(1, core::sync::atomic::Ordering::SeqCst);
484+
log::debug!("FdTable::drop() - PTY master fd {} (pty {}) refcount {} -> {}",
485+
i, pty_num, old_count, old_count - 1);
486+
if old_count == 1 {
487+
crate::tty::pty::release(pty_num);
488+
log::debug!("FdTable::drop() - released PTY {} (last master closed)", pty_num);
489+
}
490+
}
476491
}
477492
FdKind::PtySlave(_pty_num) => {
478493
// PTY slave doesn't own the pair, just decrement reference

kernel/src/process/process.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -275,9 +275,16 @@ impl Process {
275275
log::debug!("Process::close_all_fds() - released devpts directory fd {}", fd);
276276
}
277277
FdKind::PtyMaster(pty_num) => {
278-
// PTY master cleanup - release the PTY pair when master closes
279-
crate::tty::pty::release(pty_num);
280-
log::debug!("Process::close_all_fds() - released PTY master fd {} (pty {})", fd, pty_num);
278+
// PTY master cleanup - decrement refcount, only release when all masters closed
279+
if let Some(pair) = crate::tty::pty::get(pty_num) {
280+
let old_count = pair.master_refcount.fetch_sub(1, core::sync::atomic::Ordering::SeqCst);
281+
log::debug!("Process::close_all_fds() - PTY master fd {} (pty {}) refcount {} -> {}",
282+
fd, pty_num, old_count, old_count - 1);
283+
if old_count == 1 {
284+
crate::tty::pty::release(pty_num);
285+
log::debug!("Process::close_all_fds() - released PTY {} (last master closed)", pty_num);
286+
}
287+
}
281288
}
282289
FdKind::PtySlave(_pty_num) => {
283290
// PTY slave doesn't own the pair, just decrement reference

kernel/src/syscall/handlers.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -783,7 +783,8 @@ pub fn sys_read(fd: u64, buf_ptr: u64, count: u64) -> SyscallResult {
783783
}
784784
FdKind::TcpConnection(conn_id) => {
785785
// Read from TCP connection
786-
// First drain loopback queue in case there are pending packets
786+
// First process any pending network packets
787+
crate::net::process_rx();
787788
crate::net::drain_loopback_queue();
788789
let mut user_buf = alloc::vec![0u8; count as usize];
789790
match crate::net::tcp::tcp_recv(conn_id, &mut user_buf) {
@@ -2301,6 +2302,12 @@ pub fn sys_poll(fds_ptr: u64, nfds: u64, _timeout: i32) -> SyscallResult {
23012302

23022303
log::debug!("sys_poll: fds_ptr={:#x}, nfds={}, timeout={}", fds_ptr, nfds, _timeout);
23032304

2305+
// Process any pending network packets before checking fd readiness.
2306+
// This is critical for TCP listeners - without this, incoming SYN packets
2307+
// remain unprocessed in the e1000 driver buffer and accept() never sees connections.
2308+
crate::net::process_rx();
2309+
crate::net::drain_loopback_queue();
2310+
23042311
// Validate parameters
23052312
if fds_ptr == 0 && nfds > 0 {
23062313
return SyscallResult::Err(14); // EFAULT
@@ -2429,6 +2436,12 @@ pub fn sys_select(
24292436
nfds, readfds_ptr, writefds_ptr, exceptfds_ptr, _timeout_ptr
24302437
);
24312438

2439+
// Process any pending network packets before checking fd readiness.
2440+
// This is critical for TCP listeners - without this, incoming SYN packets
2441+
// remain unprocessed in the e1000 driver buffer and accept() never sees connections.
2442+
crate::net::process_rx();
2443+
crate::net::drain_loopback_queue();
2444+
24322445
// Validate nfds - must be non-negative and <= 64 (we only support u64 bitmaps)
24332446
if nfds < 0 {
24342447
log::debug!("sys_select: Invalid nfds {}", nfds);

kernel/src/syscall/pipe.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,19 @@ pub fn sys_close(fd: i32) -> SyscallResult {
192192
log::debug!("sys_close: Closed TCP connection fd={}", fd);
193193
}
194194
FdKind::PtyMaster(pty_num) => {
195-
// PTY master cleanup - release the PTY pair when master closes
196-
crate::tty::pty::release(pty_num);
197-
log::debug!("sys_close: Closed PTY master fd={} (pty {})", fd, pty_num);
195+
// PTY master cleanup - decrement refcount, only release when all masters closed
196+
if let Some(pair) = crate::tty::pty::get(pty_num) {
197+
let old_count = pair.master_refcount.fetch_sub(1, core::sync::atomic::Ordering::SeqCst);
198+
log::debug!("sys_close: PTY master fd={} (pty {}) refcount {} -> {}",
199+
fd, pty_num, old_count, old_count - 1);
200+
if old_count == 1 {
201+
// Last reference - release the PTY pair
202+
crate::tty::pty::release(pty_num);
203+
log::debug!("sys_close: Released PTY {} (last master closed)", pty_num);
204+
}
205+
} else {
206+
log::warn!("sys_close: PTY {} not found for master fd={}", pty_num, fd);
207+
}
198208
}
199209
FdKind::PtySlave(pty_num) => {
200210
// PTY slave doesn't own the pair, just log closure

kernel/src/syscall/socket.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,10 @@ pub fn sys_recvfrom(
312312
src_addr_ptr: u64,
313313
addrlen_ptr: u64,
314314
) -> SyscallResult {
315+
// Process any pending network packets before checking for received data.
316+
crate::net::process_rx();
317+
crate::net::drain_loopback_queue();
318+
315319
// Validate buffer pointer
316320
if buf_ptr == 0 {
317321
return SyscallResult::Err(EFAULT as u64);
@@ -471,6 +475,11 @@ pub fn sys_listen(fd: u64, backlog: u64) -> SyscallResult {
471475
pub fn sys_accept(fd: u64, addr_ptr: u64, addrlen_ptr: u64) -> SyscallResult {
472476
log::debug!("sys_accept: fd={}", fd);
473477

478+
// Process any pending network packets before checking for connections.
479+
// This ensures incoming SYN packets are handled even if IRQ hasn't fired.
480+
crate::net::process_rx();
481+
crate::net::drain_loopback_queue();
482+
474483
// Get current thread and process
475484
let current_thread_id = match crate::per_cpu::current_thread() {
476485
Some(thread) => thread.id,

run.sh

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
#!/bin/bash
2-
# Start Breenix in interactive mode
3-
cargo run -p xtask -- interactive "$@"
2+
# Start Breenix in interactive mode with telnet port forwarding (2323)
3+
# Port 2323 is forwarded automatically by qemu-uefi
4+
#
5+
# Two windows:
6+
# - QEMU window: Type commands here (PS/2 keyboard -> shell)
7+
# - This terminal: Watch serial output (debug messages)
8+
cargo run --release --features testing,external_test_bins,interactive --bin qemu-uefi -- -display cocoa -serial mon:stdio "$@"

scripts/create_ext2_disk.sh

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,11 @@ if [[ "$(uname)" == "Darwin" ]]; then
116116
echo " WARNING: init_shell.elf not found"
117117
fi
118118
119-
# Copy telnetd for remote access
119+
# Copy telnetd for remote access (system daemon, goes in /sbin)
120120
if [ -f /binaries/telnetd.elf ]; then
121-
cp /binaries/telnetd.elf /mnt/ext2/bin/telnetd
122-
chmod 755 /mnt/ext2/bin/telnetd
123-
echo " /bin/telnetd installed"
121+
cp /binaries/telnetd.elf /mnt/ext2/sbin/telnetd
122+
chmod 755 /mnt/ext2/sbin/telnetd
123+
echo " /sbin/telnetd installed"
124124
else
125125
echo " WARNING: telnetd.elf not found"
126126
fi
@@ -234,11 +234,11 @@ else
234234
echo " /bin/init_shell installed"
235235
fi
236236

237-
# Copy telnetd for remote access
237+
# Copy telnetd for remote access (system daemon, goes in /sbin)
238238
if [ -f "$USERSPACE_DIR/telnetd.elf" ]; then
239-
cp "$USERSPACE_DIR/telnetd.elf" "$MOUNT_DIR/bin/telnetd"
240-
chmod 755 "$MOUNT_DIR/bin/telnetd"
241-
echo " /bin/telnetd installed"
239+
cp "$USERSPACE_DIR/telnetd.elf" "$MOUNT_DIR/sbin/telnetd"
240+
chmod 755 "$MOUNT_DIR/sbin/telnetd"
241+
echo " /sbin/telnetd installed"
242242
fi
243243

244244
# Create test files
@@ -297,6 +297,8 @@ if [[ -f "$OUTPUT_FILE" ]]; then
297297
echo " /sbin/true, /bin/false - exit status coreutils"
298298
echo " /bin/head, tail, wc, which - text processing coreutils"
299299
echo " /bin/hello_world - exec test binary (exit code 42)"
300+
echo " /bin/init_shell - interactive shell"
301+
echo " /sbin/telnetd - telnet daemon"
300302
echo " /hello.txt - test file (1 line)"
301303
echo " /lines.txt - multi-line test file (15 lines) for head/tail/wc"
302304
echo " /test/nested.txt - nested test file"

testdata/ext2.img

0 Bytes
Binary file not shown.

userspace/tests/poll_test.rs

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ const SYS_READ: u64 = 2;
1414
const SYS_CLOSE: u64 = 6;
1515
const SYS_POLL: u64 = 7;
1616
const SYS_PIPE: u64 = 22;
17+
const SYS_SOCKET: u64 = 41;
18+
const SYS_CONNECT: u64 = 42;
19+
const SYS_BIND: u64 = 49;
20+
const SYS_LISTEN: u64 = 50;
1721

1822
// Poll event constants
1923
const POLLIN: i16 = 0x0001;
@@ -23,6 +27,9 @@ const POLLERR: i16 = 0x0008;
2327
const POLLHUP: i16 = 0x0010;
2428
const POLLNVAL: i16 = 0x0020;
2529

30+
const AF_INET: i32 = 2;
31+
const SOCK_STREAM: i32 = 1;
32+
2633
// pollfd structure
2734
#[repr(C)]
2835
#[derive(Clone, Copy, Debug, Default)]
@@ -32,6 +39,26 @@ struct PollFd {
3239
revents: i16,
3340
}
3441

42+
#[repr(C)]
43+
#[derive(Clone, Copy)]
44+
struct SockAddrIn {
45+
sin_family: u16,
46+
sin_port: u16,
47+
sin_addr: [u8; 4],
48+
sin_zero: [u8; 8],
49+
}
50+
51+
impl SockAddrIn {
52+
fn new(addr: [u8; 4], port: u16) -> Self {
53+
Self {
54+
sin_family: AF_INET as u16,
55+
sin_port: port.to_be(),
56+
sin_addr: addr,
57+
sin_zero: [0; 8],
58+
}
59+
}
60+
}
61+
3562
// Syscall wrappers
3663
#[inline(always)]
3764
unsafe fn syscall1(n: u64, arg1: u64) -> u64 {
@@ -51,6 +78,24 @@ unsafe fn syscall1(n: u64, arg1: u64) -> u64 {
5178
ret
5279
}
5380

81+
#[inline(always)]
82+
unsafe fn syscall2(n: u64, arg1: u64, arg2: u64) -> u64 {
83+
let ret: u64;
84+
core::arch::asm!(
85+
"int 0x80",
86+
inlateout("rax") n => ret,
87+
inlateout("rdi") arg1 => _,
88+
inlateout("rsi") arg2 => _,
89+
out("rcx") _,
90+
out("rdx") _,
91+
out("r8") _,
92+
out("r9") _,
93+
out("r10") _,
94+
out("r11") _,
95+
);
96+
ret
97+
}
98+
5499
#[inline(always)]
55100
unsafe fn syscall3(n: u64, arg1: u64, arg2: u64, arg3: u64) -> u64 {
56101
let ret: u64;
@@ -363,8 +408,97 @@ pub extern "C" fn _start() -> ! {
363408
}
364409
write_str(" OK: Multiple fds poll works correctly\n");
365410

411+
// Phase 8: Poll TCP listener for first-call readiness
412+
write_str("Phase 8: Polling TCP listener for POLLIN...\n");
413+
let server_fd = unsafe { syscall3(SYS_SOCKET, AF_INET as u64, SOCK_STREAM as u64, 0) } as i64;
414+
if server_fd < 0 {
415+
write_str(" socket() returned: ");
416+
write_num(server_fd);
417+
write_str("\n");
418+
fail("socket() failed");
419+
}
420+
let server_fd = server_fd as i32;
421+
422+
let server_addr = SockAddrIn::new([0, 0, 0, 0], 9091);
423+
let bind_ret = unsafe {
424+
syscall3(
425+
SYS_BIND,
426+
server_fd as u64,
427+
&server_addr as *const SockAddrIn as u64,
428+
core::mem::size_of::<SockAddrIn>() as u64,
429+
)
430+
} as i64;
431+
if bind_ret < 0 {
432+
write_str(" bind() returned: ");
433+
write_num(bind_ret);
434+
write_str("\n");
435+
fail("bind() failed");
436+
}
437+
438+
let listen_ret = unsafe { syscall2(SYS_LISTEN, server_fd as u64, 16) } as i64;
439+
if listen_ret < 0 {
440+
write_str(" listen() returned: ");
441+
write_num(listen_ret);
442+
write_str("\n");
443+
fail("listen() failed");
444+
}
445+
446+
let client_fd = unsafe { syscall3(SYS_SOCKET, AF_INET as u64, SOCK_STREAM as u64, 0) } as i64;
447+
if client_fd < 0 {
448+
write_str(" client socket() returned: ");
449+
write_num(client_fd);
450+
write_str("\n");
451+
fail("client socket() failed");
452+
}
453+
let client_fd = client_fd as i32;
454+
455+
let loopback_addr = SockAddrIn::new([127, 0, 0, 1], 9091);
456+
let connect_ret = unsafe {
457+
syscall3(
458+
SYS_CONNECT,
459+
client_fd as u64,
460+
&loopback_addr as *const SockAddrIn as u64,
461+
core::mem::size_of::<SockAddrIn>() as u64,
462+
)
463+
} as i64;
464+
if connect_ret < 0 {
465+
write_str(" connect() returned: ");
466+
write_num(connect_ret);
467+
write_str("\n");
468+
fail("connect() failed");
469+
}
470+
471+
let mut listen_fds = [PollFd {
472+
fd: server_fd,
473+
events: POLLIN,
474+
revents: 0,
475+
}];
476+
let poll_ret = unsafe {
477+
syscall3(SYS_POLL, listen_fds.as_mut_ptr() as u64, 1, 0)
478+
} as i64;
479+
480+
write_str(" poll() returned: ");
481+
write_num(poll_ret);
482+
write_str(", revents=");
483+
write_hex(listen_fds[0].revents);
484+
write_str("\n");
485+
486+
if poll_ret < 0 {
487+
fail("poll() on listener failed");
488+
}
489+
if poll_ret != 1 {
490+
fail("poll() should return 1 for listener readiness");
491+
}
492+
if listen_fds[0].revents & POLLIN == 0 {
493+
fail("Listener should have POLLIN set on first poll");
494+
}
495+
write_str(" OK: Listener POLLIN set on first poll\n");
496+
497+
unsafe { syscall1(SYS_CLOSE, client_fd as u64) };
498+
unsafe { syscall1(SYS_CLOSE, server_fd as u64) };
499+
366500
// Clean up
367-
write_str("Phase 8: Cleanup...\n");
501+
write_str("Phase 9: Cleanup...\n");
368502
unsafe { syscall1(SYS_CLOSE, pipefd[0] as u64) };
369503
write_str(" Closed remaining fds\n");
370504

0 commit comments

Comments
 (0)