<feed xmlns='http://www.w3.org/2005/Atom'>
<title>gbos.git/src/socket.asm, branch master</title>
<subtitle>gbos - a tiny Unix-flavored OS for the Game Boy Color (kernel, shell, coreutils in C, RAM filesystem)</subtitle>
<id>https://git.sl0p.foo/gbos.git/atom/src/socket.asm?h=master</id>
<link rel='self' href='https://git.sl0p.foo/gbos.git/atom/src/socket.asm?h=master'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/gbos.git/'/>
<updated>2026-07-17T22:31:27Z</updated>
<entry>
<title>net: reject out-of-order TCP segments (accept only seq == rcv_nxt, re-ACK dups)</title>
<updated>2026-07-17T22:31:27Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-07-17T22:31:27Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/gbos.git/commit/?id=d0300a713356ab524d58e32a0e8ae63c7662feed'/>
<id>urn:sha1:d0300a713356ab524d58e32a0e8ae63c7662feed</id>
<content type='text'>
Retransmitted segments (GB ACKs lag behind slow LCD rendering, so real
servers do retransmit) were accepted as fresh data: the same line rendered
again on every retransmit, and rcv_nxt over-advanced so every later outgoing
segment carried an ACK beyond the peer's snd_nxt - which real stacks drop,
silently wedging the session ('/join does nothing' until reconnect).
</content>
</entry>
<entry>
<title>net: SYS_POLLCON + larger console ring for link-injected input</title>
<updated>2026-07-17T18:22:34Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-07-17T18:22:34Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/gbos.git/commit/?id=bf647581fbad727ad22021d345d3955abf44c2f3'/>
<id>urn:sha1:bf647581fbad727ad22021d345d3955abf44c2f3</id>
<content type='text'>
Programs that own their main loop (the IRC client) need to poll for
typed input without blocking. pollin() only sees the on-screen keyboard;
bytes injected over the link (gbhub 'type', for scripted/hub-driven
sessions) land in the kernel console ring, previously only drained by the
blocking KGetc path. Add SYS_POLLCON: a non-blocking con_pop for
userland.

Also enlarge that console ring 16 -&gt; 64. One net_pump drains an entire
serial burst into the ring at once, so a whole injected command line has
to fit or bytes are dropped and lines merge (a 20-char command came out
truncated and glued to the next). 64 covers a full line; mask stays a
power of two.
</content>
</entry>
<entry>
<title>net: fix truncated TCP field offsets corrupting multi-segment recv</title>
<updated>2026-07-17T18:22:07Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-07-17T18:22:07Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/gbos.git/commit/?id=d94b18652ddfb264aa9a34514ea790230d80bcc2'/>
<id>urn:sha1:d94b18652ddfb264aa9a34514ea790230d80bcc2</id>
<content type='text'>
SK_STATE/SK_SND/SK_RCV sat at struct offsets 305/306/310, past the
288-byte SK_RXBUF. But the field accessors add the offset as an 8-bit
immediate (add SK_x; ld l,a), so rgbasm truncated 305-&gt;49, 306-&gt;50,
310-&gt;54 (the -Wtruncation warnings we'd been ignoring) - putting STATE
and the sequence numbers *inside* RXBUF at offsets 49/50/54.

Any TCP segment with &gt;=33 payload bytes therefore overwrote the
connection's own STATE and rcv_nxt/snd_nxt with message text. The first
segment of a stream landed, its data clobbered STATE to a garbage value,
and every subsequent segment was dropped because tcp_in no longer saw
ESTABLISHED - so multi-segment TCP receives (an IRC MOTD, any HTTP body
past one segment) silently stalled. wget appeared to 'work' only because
a single-segment reply plus a never-honored FIN still printed once.

Fix: reorder the struct so STATE/SND/RCV precede the big RXBUF, keeping
every field offset &lt; 256 (SK_SIZE unchanged at 314, all accessors are
symbolic). Zero -Wtruncation warnings remain. Verified an 11-line IRC
registration burst now arrives intact.
</content>
</entry>
<entry>
<title>net: unbreak ping against hosts that intermittently drop ICMP</title>
<updated>2026-07-17T17:45:13Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-07-17T17:45:13Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/gbos.git/commit/?id=4f038ecad87777bc1d74f8f93f267da8cd396446'/>
<id>urn:sha1:4f038ecad87777bc1d74f8f93f267da8cd396446</id>
<content type='text'>
Pinging sl0p.foo through gbhub looked hung: DNS resolved, then nothing.
Packet-tracing showed the echo request leaving the hub's TUN and eth0
correctly NATed every time - but 80.78.19.56 blackholes ICMP for all
ids in windows of tens of seconds (provider rate limiting). One lost
reply wedged ping for minutes because net_recv's pump-counted timeout
is effectively unbounded at native emulation speed.

Two fixes:

- NET_RECVNB (op 8): non-blocking recv - one RX pump, $FE if nothing
  buffered, same delivery/EOF semantics as NET_RECV otherwise. ping now
  waits &lt;=~2s per seq (net_recv_nb + msleep loop), prints 'seq=N
  timeout' and moves on, like real ping.

- ICMP echo id was hardcoded $1234 for every GB, every boot, so all
  sessions produced byte-identical flows - hostile to NAT conntrack
  (keyed on icmp id). wNetEchoId is now our host octet + rDIV timing
  noise sampled at DHCP lease, distinct per GB and per boot.

Verified: 8 back-to-back native-speed gbhub runs, zero hangs; a run
that hit a blackhole window printed seq=1 timeout then recovered to
3/4 received. GB&lt;-&gt;GB ping and DNS/DHCP unaffected.
</content>
</entry>
<entry>
<title>net: pump the network from the console wait (answer pings at the prompt)</title>
<updated>2026-07-17T13:12:11Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-07-17T13:12:11Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/gbos.git/commit/?id=f68fc872755189bc287a5f7ca838dbd9f45c8353'/>
<id>urn:sha1:f68fc872755189bc287a5f7ca838dbd9f45c8353</id>
<content type='text'>
A Game Boy sitting at the shell prompt now services the network instead of
being deaf until you run netd: KGetc (the console input wait) pumps net_pump
each poll, so inbound pings are auto-answered while idle. net_pump gained an
in-frame model and routes any non-framed link bytes to a small console-input
ring (con_push/con_pop), so a headless-injected command still reaches the shell
while SLIP frames go to the stack. Removes the old SLIP-skip-in-KGetc hack.

Verified: two Game Boys on the hub, GB0 idle at the prompt (no netd), GB1
`ping 10.0.0.2` -&gt; 4/4 replies, routed GB1-&gt;hub-&gt;GB0-&gt;hub-&gt;GB1.

(Separate, pre-existing: gbhub *spawn* mode and windowed gbjoin can drop an idle
link socket - under investigation; daemon mode + headless is solid.)
</content>
</entry>
<entry>
<title>net: DHCP client - lease the IP at boot instead of hardcoding it</title>
<updated>2026-07-17T12:11:22Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-07-17T12:11:22Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/gbos.git/commit/?id=a2cb756693229c7880d926d2cdde2f838c697415'/>
<id>urn:sha1:a2cb756693229c7880d926d2cdde2f838c697415</id>
<content type='text'>
The address is no longer baked in. net_init starts at 0.0.0.0; a DHCP client
runs as the first thing on boot (init/shell forks it and waits), and only once
it has a lease (or gives up) does the prompt appear.

Kernel:
- IP is configurable: 0.0.0.0 until leased; NET_SETIP op stores it.
- 16-bit frame length. DHCP/BOOTP packets are ~272 bytes, over the old 255-byte
  frame cap, so net_slip_send takes a 16-bit length, net_pump assembles into a
  320-byte buffer with a 16-bit wNetRxLen, and udp_send writes a 16-bit IP total.
  Payloads stay &lt;=255 (kept small on purpose) so the per-protocol datalen math
  is unchanged. wNetTx/wNetRxBuf 256-&gt;320, SK_RXBUF 208-&gt;288.

Userland:
- c/dhcp.c: DISCOVER-&gt;OFFER-&gt;REQUEST-&gt;ACK over a UDP socket, then net_setip();
  times out gracefully (shell still boots) if there's no server. sh.c runs it
  before the prompt.

Bridge (self-contained DHCP server, no dnsmasq):
- tunbridge.py + netboot intercept UDP-&gt;:67 and answer OFFER/ACK leasing
  10.0.0.2 (gateway 10.0.0.1); everything else is bridged/NATed as before.

Regression-tested ICMP/UDP/TCP after the 16-bit change. Verified end to end:
    dhcp: discovering
    dhcp: leased 10.0.0.2
    /# ping 10.0.0.1   -&gt; 4/4 (traffic from the leased address)
</content>
</entry>
<entry>
<title>net: TCP sockets in the kernel - a Game Boy fetches HTTP over real TCP</title>
<updated>2026-07-17T11:06:38Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-07-17T11:06:38Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/gbos.git/commit/?id=c0e74377af490b93aa452a19652c61cead9c39ff'/>
<id>urn:sha1:c0e74377af490b93aa452a19652c61cead9c39ff</id>
<content type='text'>
Kernel TCP client on the socket layer: net_connect(SOCK_TCP) runs the 3-way
handshake in-kernel, send()/recv() drive the byte stream, close() does the FIN.

- Socket gains state + 32-bit snd_nxt/rcv_nxt (big-endian, add-with-carry-fold).
- tcp_send_seg builds IP+TCP with a pseudo-header checksum; the SYN carries an
  MSS option (200) so the peer never sends a segment larger than our 256-byte
  frame buffer (we don't do IP reassembly).
- tcp_in state machine: SYN_SENT-&gt;ESTABLISHED on SYN-ACK, buffers in-order data
  and ACKs it, handles FIN -&gt; recv() returns 0 (EOF).
- No retransmission: the GB&lt;-&gt;host link is lossless and the host's real TCP
  owns the internet side - which removes TCP's hardest part.
- net_pump now processes one frame per call so recv drains each segment before
  the next arrives (single rx slot, no overwrite).

wget.c is now a thin socket client: connect -&gt; send "GET / HTTP/1.0" -&gt; recv to
EOF -&gt; print. Verified end to end against a host HTTP server:

    /# wget 10.0.0.1
    HTTP/1.0 200 OK
    Hello from a real HTTP server, fetched by a Game Boy!

with a clean SYN/SYN-ACK/ACK ... PSH ... FIN/ACK trace on the wire.
</content>
</entry>
<entry>
<title>net: UDP sockets in the kernel + a DNS resolver (real hostname lookups)</title>
<updated>2026-07-17T10:54:43Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-07-17T10:54:43Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/gbos.git/commit/?id=26a2ccb8368a9968b6ec16068ad68dd0edb40641'/>
<id>urn:sha1:26a2ccb8368a9968b6ec16068ad68dd0edb40641</id>
<content type='text'>
Adds UDP to the kernel socket layer on top of the ICMP core:
- net_sum() (raw folded sum) split out of net_cksum() so a UDP pseudo-header
  (src/dst IP + proto + length) can seed the segment checksum.
- udp_send: builds IP+UDP with the pseudo-header checksum; NET_BIND sets the
  local/source port; net_connect sets the peer.
- udp_in: demuxes inbound UDP by destination port to the bound socket
  (net_find_udp), delivers the payload + source addr.

Also fixes a real recv bug: net_pump clobbers BC/DE/HL, so the old recv timeout
counted in registers and was effectively random. recv now counts in WRAM.

New `nslookup HOST` (PROG_NSLOOKUP=28, bank 30): builds a DNS A query and parses
the answer (with 0xC0 name-compression) entirely in userland over a UDP socket -
the kernel never sees DNS, just UDP. Verified through the bridge NAT:
    /# nslookup example.com   -&gt;  example.com -&gt; 172.66.147.243

This gives us name resolution for the TCP/HTTP demo next.
</content>
</entry>
<entry>
<title>net: proper socket API in the kernel (SYS_NET) - no more SLIP in userland</title>
<updated>2026-07-17T10:39:01Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-07-17T10:39:01Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/gbos.git/commit/?id=09ae37313a8b6d567a05a3ae886de78e88cd1afe'/>
<id>urn:sha1:09ae37313a8b6d567a05a3ae886de78e88cd1afe</id>
<content type='text'>
The network stack moves into the kernel. src/socket.asm owns SLIP framing,
IPv4, RFC1071 checksums, and ICMP; programs now speak a socket API through one
syscall (SYS_NET, DE=&amp;netreq dispatched by op): net_socket/connect/send/recv/
close/poll (c/sock.h). No program touches SLIP, IP headers, or checksums.

- Socket table (4 sockets) + tx/rx buffers in WRAM0; our IP = 10.0.0.2.
- net_pump: drains the link, reassembles SLIP frames, demuxes IPv4. Inbound
  ICMP echo requests are auto-answered in-kernel, so the GB replies to pings
  whenever any process pumps RX.
- ICMP sockets: send() emits an echo request to the connected peer; recv()
  returns the matching reply (with a spin/yield timeout).

ping.c is now a ~15-line socket client; netd.c is just `for(;;){net_poll();
yield();}`. Verified over tunbridge:
    /# ping 1.1.1.1     -&gt; replies from the real internet (kernel builds it all)
    host# ping 10.0.0.2 -&gt; 4/4, 0% loss (kernel auto-answers)

Gotchas recorded: gbos.inc isn't a make dep (touch asm after editing); this
crt0 doesn't copy initializers (fill arrays at runtime); and the arg string at
0xA000 overlaps _DATA, so parse targets must sit past it (big buffer first).
UDP and TCP sockets build on this same core next.
</content>
</entry>
</feed>
