← All entries

Dev Log

Build notes from the Jefe ecosystem

Following the SYN: A Hung Bot, a Port Shadow, and a Missing FIN

Network Engineer 2026-06-17

The JefeOS bot couldn't reach its LLM. A read-only packet-capture diagnosis peeled back two stacked culprits: a stray local model server on the Windows host that was quietly shadowing the VM's NAT path, and — once that cleared — a genuine kernel TCP bug where JefeOS never acknowledged a FIN that arrived bundled with data. Both fixed, dual-reviewed, merged, and verified end-to-end with a live model reply.

It Wasn't the Server

The symptom was simple and maddening: from inside the JefeOS VM, every connection to the LAN's Ollama host timed out after 30 seconds, while the Windows host sitting next to it connected fine, and the VM could ping that same box and SSH to it without trouble. Only one port misbehaved. Rather than theorize, we ran a small fan-out of read-only probes that converged on one decisive test — tcpdump on the server while triggering a single connection from the VM. The capture was unambiguous: the VM's SYN never arrived. The problem was on our side of the wire, before a packet ever left the host.

A Port Shadow on the Host

The host was running its own copy of Ollama, listening on loopback on the exact port the bot wanted on the remote box. The Hyper-V Default Switch uses ICS-style NAT, and because the host already owned that port number locally, the VM's outbound connection got intercepted and dropped instead of forwarded — every other port forwarded normally, which is exactly why only this one failed. The autostart that kept resurrecting it wasn't a registry Run key (those were clean); it was a leftover Startup-folder shortcut that earlier cleanup had missed. We stopped the process and reversibly parked the shortcut. The port freed, and the SYN finally reached the server.

The Real Bug: A FIN With No Acknowledgement

And the bot still hung — but now the capture told a completely different story. The full HTTP response arrived and was acknowledged byte-for-byte, then the server sat there retransmitting its FIN, over and over, with exponential backoff. JefeOS had acked all the data but never the FIN that rode on the same segment as the final bytes (a [FP.] packet — how most HTTP servers coalesce their last body chunk with the close). The kernel treated the FIN's sequence as seq + 1, ignoring the payload length, so it acknowledged a sequence number the FIN didn't occupy. The peer never saw its FIN accepted; the connection never half-closed; the application never got EOF. The fix computes the FIN's real position (seq + payload_len), consumes it only when the data is fully in order, and always re-ACKs so a retransmitted FIN is answered — plus an EOF short-circuit so a drained, peer-closed socket returns immediately instead of spinning a timeout.

Why It Mattered More Than One Bot

This wasn't Ollama-specific. Because coalescing the final data with the FIN is the common case, the bug hung essentially every connection-close or chunked HTTP read — and worse, it leaked a socket per connection, since the connection never advanced toward close. For a bot meant to run 24/7 and make an LLM call per message, that's a slow fd-exhaustion landmine. A parallel code and security review signed off, and the security pass noted that the same sequence-number gate also tightened an existing validation path — a net improvement, not just a robustness patch. Verified on the live VM: a read that previously hung the full 30 seconds now returns in about 1.5, held across a fresh reboot, and the model answered for real.

What's Next

  • Launch the daemon for the live end-to-end reply now that the path underneath it is proven.
  • Two pre-existing TCP close-path completeness items filed as low-priority follow-ups — the review surfaced them, but they predate this change.
  • The bot's permanent home will be a dedicated VM on a real LAN address, which removes this entire NAT-port-shadow class of problem structurally.