Skip to content

Commit 2532624

Browse files
ryanbreenclaude
andauthored
fix(net): add packet polling to networking syscalls for first-call readiness (#99)
* fix(ext2): add init_shell and telnetd to filesystem The telnetd server was trying to exec /bin/init_shell but it wasn't in the ext2 filesystem, causing the shell to fail to start over telnet. Now ext2 contains: - /bin/init_shell (32KB) - interactive shell - /bin/telnetd (9KB) - telnet server Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * 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> * fix(tcp): add refcount for fork/close pattern + remove debug output TCP connections now use reference counting similar to PTY: - Add refcount field to TcpConnection (AtomicUsize) - tcp_add_ref() increments count during fork - tcp_close() only sends FIN when last reference drops - FdTable::clone() calls tcp_add_ref() for TCP fds This fixes telnetd where the child closes inherited socket fds, which was prematurely closing the parent's connection. Also removes debug output: - TSS RSP0 updated messages from gdt.rs - tcp_add_ref/tcp_close debug logs from tcp.rs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent db6d3c6 commit 2532624

File tree

13 files changed

+466
-22
lines changed

13 files changed

+466
-22
lines changed

kernel/src/gdt.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -178,13 +178,7 @@ pub fn set_kernel_stack(stack_top: VirtAddr) {
178178
let tss_ptr = TSS_PTR.load(Ordering::Acquire);
179179
if !tss_ptr.is_null() {
180180
unsafe {
181-
let old_stack = (*tss_ptr).privilege_stack_table[0];
182181
(*tss_ptr).privilege_stack_table[0] = stack_top;
183-
crate::serial_println!(
184-
"TSS RSP0 updated: {:#x} -> {:#x}",
185-
old_stack.as_u64(),
186-
stack_top.as_u64()
187-
);
188182
}
189183
} else {
190184
panic!("TSS not initialized");

kernel/src/ipc/fd.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,12 +205,24 @@ 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+
}
222+
FdKind::TcpConnection(conn_id) => {
223+
// Increment TCP connection reference count for the clone
224+
crate::net::tcp::tcp_add_ref(conn_id);
225+
}
214226
_ => {}
215227
}
216228
}
@@ -470,9 +482,16 @@ impl Drop for FdTable {
470482
log::debug!("FdTable::drop() - releasing devpts directory fd {}", i);
471483
}
472484
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);
485+
// PTY master cleanup - decrement refcount, only release when all masters closed
486+
if let Some(pair) = crate::tty::pty::get(pty_num) {
487+
let old_count = pair.master_refcount.fetch_sub(1, core::sync::atomic::Ordering::SeqCst);
488+
log::debug!("FdTable::drop() - PTY master fd {} (pty {}) refcount {} -> {}",
489+
i, pty_num, old_count, old_count - 1);
490+
if old_count == 1 {
491+
crate::tty::pty::release(pty_num);
492+
log::debug!("FdTable::drop() - released PTY {} (last master closed)", pty_num);
493+
}
494+
}
476495
}
477496
FdKind::PtySlave(_pty_num) => {
478497
// PTY slave doesn't own the pair, just decrement reference

kernel/src/net/tcp.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,8 @@ pub struct TcpConnection {
296296
pub send_shutdown: bool,
297297
/// True if SHUT_RD was called (no more receiving)
298298
pub recv_shutdown: bool,
299+
/// Reference count for fork() support - connection is only closed when last fd is closed
300+
pub refcount: core::sync::atomic::AtomicUsize,
299301
}
300302

301303
impl TcpConnection {
@@ -321,6 +323,7 @@ impl TcpConnection {
321323
owner_pid,
322324
send_shutdown: false,
323325
recv_shutdown: false,
326+
refcount: core::sync::atomic::AtomicUsize::new(1),
324327
}
325328
}
326329

@@ -352,6 +355,7 @@ impl TcpConnection {
352355
owner_pid,
353356
send_shutdown: false,
354357
recv_shutdown: false,
358+
refcount: core::sync::atomic::AtomicUsize::new(1),
355359
}
356360
}
357361
}
@@ -1034,13 +1038,30 @@ pub fn tcp_shutdown(conn_id: &ConnectionId, shut_rd: bool, shut_wr: bool) {
10341038
}
10351039
}
10361040

1037-
/// Close a connection
1041+
/// Increment reference count on a TCP connection (called during fork)
1042+
pub fn tcp_add_ref(conn_id: &ConnectionId) {
1043+
let connections = TCP_CONNECTIONS.lock();
1044+
if let Some(conn) = connections.get(conn_id) {
1045+
conn.refcount.fetch_add(1, core::sync::atomic::Ordering::SeqCst);
1046+
}
1047+
}
1048+
1049+
/// Close a connection (decrement refcount, only actually close when last reference dropped)
10381050
pub fn tcp_close(conn_id: &ConnectionId) -> Result<(), &'static str> {
10391051
let config = super::config();
10401052

10411053
let mut connections = TCP_CONNECTIONS.lock();
10421054
let conn = connections.get_mut(conn_id).ok_or("Connection not found")?;
10431055

1056+
// Decrement reference count
1057+
let old_count = conn.refcount.fetch_sub(1, core::sync::atomic::Ordering::SeqCst);
1058+
1059+
// Only actually close when last reference is dropped
1060+
if old_count > 1 {
1061+
return Ok(());
1062+
}
1063+
1064+
// Last reference - actually close the connection
10441065
match conn.state {
10451066
TcpState::Established => {
10461067
// Send FIN

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: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,24 @@ if [[ "$(uname)" == "Darwin" ]]; then
107107
echo " WARNING: hello_world.elf not found"
108108
fi
109109
110+
# Copy init_shell for interactive use and telnet
111+
if [ -f /binaries/init_shell.elf ]; then
112+
cp /binaries/init_shell.elf /mnt/ext2/bin/init_shell
113+
chmod 755 /mnt/ext2/bin/init_shell
114+
echo " /bin/init_shell installed"
115+
else
116+
echo " WARNING: init_shell.elf not found"
117+
fi
118+
119+
# Copy telnetd for remote access (system daemon, goes in /sbin)
120+
if [ -f /binaries/telnetd.elf ]; then
121+
cp /binaries/telnetd.elf /mnt/ext2/sbin/telnetd
122+
chmod 755 /mnt/ext2/sbin/telnetd
123+
echo " /sbin/telnetd installed"
124+
else
125+
echo " WARNING: telnetd.elf not found"
126+
fi
127+
110128
# Create test files for filesystem testing
111129
echo "Hello from ext2!" > /mnt/ext2/hello.txt
112130
echo "Truncate test file" > /mnt/ext2/trunctest.txt
@@ -209,6 +227,20 @@ else
209227
echo " /bin/hello_world installed"
210228
fi
211229

230+
# Copy init_shell for interactive use and telnet
231+
if [ -f "$USERSPACE_DIR/init_shell.elf" ]; then
232+
cp "$USERSPACE_DIR/init_shell.elf" "$MOUNT_DIR/bin/init_shell"
233+
chmod 755 "$MOUNT_DIR/bin/init_shell"
234+
echo " /bin/init_shell installed"
235+
fi
236+
237+
# Copy telnetd for remote access (system daemon, goes in /sbin)
238+
if [ -f "$USERSPACE_DIR/telnetd.elf" ]; then
239+
cp "$USERSPACE_DIR/telnetd.elf" "$MOUNT_DIR/sbin/telnetd"
240+
chmod 755 "$MOUNT_DIR/sbin/telnetd"
241+
echo " /sbin/telnetd installed"
242+
fi
243+
212244
# Create test files
213245
echo "Hello from ext2!" > "$MOUNT_DIR/hello.txt"
214246
echo "Truncate test file" > "$MOUNT_DIR/trunctest.txt"
@@ -265,6 +297,8 @@ if [[ -f "$OUTPUT_FILE" ]]; then
265297
echo " /sbin/true, /bin/false - exit status coreutils"
266298
echo " /bin/head, tail, wc, which - text processing coreutils"
267299
echo " /bin/hello_world - exec test binary (exit code 42)"
300+
echo " /bin/init_shell - interactive shell"
301+
echo " /sbin/telnetd - telnet daemon"
268302
echo " /hello.txt - test file (1 line)"
269303
echo " /lines.txt - multi-line test file (15 lines) for head/tail/wc"
270304
echo " /test/nested.txt - nested test file"

testdata/ext2.img

0 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)