Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ netstack lets a piped connection's socket and id go once the wire is finished
and it has closed both of its pipe ends (`bridge_piped`,
`userland/netstack/src/main.rs`). A peer's reset does both at once, so a
client that still holds the stream names an id netstack no longer has, and
`handle_tcp_set_option` and `handle_tcp_get_option` answer it
`ERR_NOT_CONNECTED`. Before the socket left with its bridge both answered from
the kept socket.
`handle_tcp_set_option` answers it `ERR_NOT_CONNECTED`. Before the socket left
with its bridge it answered from the kept socket.

What a host answers `std::net::TcpStream` on a stream whose peer reset it,
after the read that reported the reset:
Expand All @@ -21,10 +20,12 @@ after the read that reported the reset:
|---|---|---|
| macOS (Darwin 27.0.0), measured | `Err(InvalidInput)`, `EINVAL` | `Ok(false)` |
| Linux, by the review of the change that made this, not measured | `Ok(())` | `Ok` |
| ToyOS, read from the code | `Err(NotConnected)` | `Err(NotConnected)` |
| ToyOS, read from the code | `Err(NotConnected)` | `Ok`, the value last set |

So the write is refused as macOS refuses it, under another kind, and the read
is refused where both hosts answer. Keeping the id until the client's close
So the write is refused as macOS refuses it, under another kind. The read asks
netstack nothing: std answers from the value its `TcpStream` holds
(`sdk/std/sys/net/connection.rs`) and libc's `getsockopt` from its socket's
entry (`userland/libc/src/socket.rs`). Keeping the id until the client's close
request is not the fix: a client that dies sends none, and netstack, having
closed its pipe ends, has nothing left that says the client is gone.

Expand Down
23 changes: 23 additions & 0 deletions issues/libc-drops-a-tcp-nodelay-set-before-connect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
status: open
kind: defect
opened: 2026-10-08
---

# libc drops a TCP_NODELAY set before connect

Read from the code, not run. `setsockopt` (`userland/libc/src/socket.rs`)
sends `TCP_NODELAY` to netstack only for a socket netstack holds, and a stream
socket is one only from `connect` or `accept`. Set on a socket not yet
connected, the option answers 0, is stored nowhere and is not sent when the
connection is made; `getsockopt` then answers 0 for it, which is true of what
netstack holds and not of what the program was told. Linux keeps the option
across `connect`. `SO_BROADCAST` on a datagram socket not yet bound is kept in
the socket's entry and sent at `bind`; this is the same case with no such
path.

**Exit**: a `TCP_NODELAY` set before `connect` reaches netstack with the
connection or is refused by name, and a guest C case reads it back after
`connect`.

**Owner**: libc, in a libc stage of its own under `issues/toyos-has-its-own-network-stack.md`: it touches only `userland/libc` and guest C cases, and lands before the guest tests that follow the move of netd onto the node.
26 changes: 26 additions & 0 deletions issues/libc-reads-an-ipv4-address-in-host-order.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
status: open
kind: defect
opened: 2026-10-08
---

# libc reads an IPv4 address in host order

`sockaddr_in`'s `s_addr` is in network byte order: its four bytes in memory
are the address's four octets. libc's `parse_sockaddr`
(`userland/libc/src/socket.rs`) takes them with `s_addr.to_be_bytes()`, which
on a little-endian machine reverses them, and `fill_sockaddr`, `getaddrinfo`
and `inet_addr` write `u32::from_be_bytes(ip)`, reversed the same way. So an
address libc made itself comes back right, and one a program made as POSIX
says does not: `inet_pton` copies the octets in order and `htonl` swaps, and
`connect` reads either backwards.

Measured in a guest on `tests/netcase`, x86-64, with a host server listening:
`connect` to `{htonl(0x0a000202)}` answered -1, and to
`{inet_addr("10.0.2.2")}` answered 0.

**Exit**: every address libc reads or writes is its octets in memory order,
and a guest C case connects to an address built with `htonl` and to one built
with `inet_pton`.

**Owner**: libc, in a libc stage of its own under `issues/toyos-has-its-own-network-stack.md`: it touches only `userland/libc` and guest C cases, and lands before the guest tests that follow the move of netd onto the node.
32 changes: 32 additions & 0 deletions issues/libc-sends-from-a-datagram-socket-it-never-bound.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
status: open
kind: defect
opened: 2026-10-08
---

# libc sends from a datagram socket it never bound

netstack holds a datagram socket from `bind`
(`toyos::net::udp_bind`), which is also where libc's socket entry gets its two
pipe ends. `sendto` (`userland/libc/src/socket.rs`) does not bind a socket
that was never bound: it writes the datagram to the entry's `tx_fd`, which is
0 and names whatever handle 0 is in the process, and only then would ask
netstack to send from socket id 0. Measured in a guest on `tests/netcase`,
x86-64, from a job of test-runner's: `sendto` answers -1 with `errno` 5
(`EIO`), and the job exits 0. Which step refused is not shown: libc answers
`EIO` for a refused write and for several of `udp_send_to`'s refusals alike.
What a process whose handle 0 takes a write sees is unmeasured. POSIX binds an unbound datagram socket to an
ephemeral port at its first send, and a C program that broadcasts, `socket`,
`setsockopt(SO_BROADCAST)`, `sendto`, does exactly this. std has no such
socket: `UdpSocket::bind` is its only constructor.

That program does not compile either: `userland/libc/include/netinet/in.h`
defines no `INADDR_BROADCAST`.

**Exit**: the first `sendto` on an unbound datagram socket binds it to a port
netstack chooses, handing over a `SO_BROADCAST` already set as `bind` does,
`netinet/in.h` defines `INADDR_BROADCAST`, and a guest C case sends from a
socket it never bound.

**Owner**: libc, in a libc stage of its own under `issues/toyos-has-its-own-network-stack.md`: it touches only `userland/libc` and guest C cases, and lands before the guest tests that follow the move of netd onto the node. The C case the track's broadcast line names for the sequence without
`bind` waits on this.
26 changes: 26 additions & 0 deletions issues/std-holds-a-streams-nodelay-per-duplicate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
status: open
kind: defect
opened: 2026-10-08
---

# std holds a stream's nodelay per duplicate

Read from the code, not run. `TcpStream::try_clone`
(`sdk/std/sys/net/connection.rs`, `duplicate`) shares the one netstack socket
between the stream and its duplicate and gives the duplicate its own copy of
`nodelay`. `set_nodelay` on either changes the socket both name and stores the
value only in the one it was called on, so `nodelay()` on the other answers
what was true before. A host's getter reads the kernel's value and cannot
differ. Two threads setting opposite values on one stream can also reach
netstack in one order and the store in the other. `UdpSocket`'s `broadcast`
is one value shared by every duplicate and locked across its request.

**Exit**: `nodelay` is one value shared by every duplicate of a stream and
locked across the request that changes it, and a guest test sets it through a
duplicate and reads it from the original.

**Owner**: whoever holds `issues/toyos-has-its-own-network-stack.md`, with the
reset-stream option test of
`issues/a-stream-its-peer-reset-refuses-the-option-requests-a-host-answers.md`,
which reads the same value.
24 changes: 24 additions & 0 deletions issues/the-socket2-forks-so-broadcast-setter-does-nothing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
status: open
kind: defect
opened: 2026-10-08
---

# The socket2 fork's SO_BROADCAST setter does nothing

Read from the fork at the commit the lock names (`1d90250`), not run. On
ToyOS, socket2's `setsockopt` (`src/sys/toyos.rs`) matches
`(SOL_SOCKET, SO_BROADCAST)` to an empty arm and answers `Ok`, and its
`getsockopt` answers 0 for it: a setter that says yes to a permission it hands
to nobody, where std's `UdpSocket::set_broadcast` and libc's `setsockopt` send
`toyos::net::udp_set_option`. No send follows it today: the backend holds no
datagram socket at all, so a program that reaches a datagram socket through
socket2 has none on ToyOS.

**Exit**: the fork's ToyOS backend either carries a datagram socket whose
`SO_BROADCAST` reaches netstack and reads back, or refuses the option by name
on a socket it cannot send a datagram from; the lock names the fork commit
that does.

**Owner**: whoever holds `issues/toyos-has-its-own-network-stack.md`; the
change is a commit on the socket2 fork's `toyos` branch.
Loading
Loading