fix: read conntrack over ctnetlink; stop reporting an unreadable table as empty (closes #1)#7
Merged
Merged
Conversation
…e as empty Closes #1. The Connections tab was permanently empty on ZimaOS. ZFW read the connection table from /proc/net/nf_conntrack and, failing that, from conntrack(8). A stock ZimaOS kernel is built with CONFIG_NF_CONNTRACK=y # CONFIG_NF_CONNTRACK_PROCFS is not set CONFIG_NF_CT_NETLINK=y and the image ships no conntrack binary, so both sources were unavailable — on a host that was tracking 877 flows at the time of measurement. The reporter's own diagnosis was right: the procfs file never appears, and ctnetlink is what the kernel offers instead. ZFW now reads ctnetlink (NETLINK_NETFILTER / IPCTNL_MSG_CT_GET, NLM_F_DUMP) in pure Go via syscall — no new dependency; go.mod still has none. Only CTA_TUPLE_ORIG is decoded, matching the original-direction-only rule of the existing parser. Attribute headers are host byte order, payloads (ports, CTA_TIMEOUT) are network order; each is decoded explicitly, with a test that fails if a port is read little-endian. /proc and conntrack(8) remain as fall-throughs for hosts that have them. Second, and the reason the reporter was sent chasing a kernel module: the handler did `if err != nil { entries = []Entry{} }` and answered HTTP 200, so "cannot read the table" was indistinguishable from "no connections". An unreadable table is now a 503 naming every source that failed, logged at ERROR, and the UI prints that reason instead of blaming a module that is running fine. The stale panel hint ("Empty on hosts where the kernel conntrack module is unavailable") is gone. Tests parse 40 real ctnetlink messages captured from a live ZimaOS 1.6.2 host (kernel 6.18.9). Only the IP addresses in that fixture were rewritten in place, to documentation ranges — every length, offset and nesting is the kernel's own. Coverage: TCP + UDP + IPv6, TCP state mapping, big-endian ports, truncation at every byte offset, the dump limit across buffers, NLMSG_DONE, and NLMSG_ERROR (EPERM) surfacing as an error rather than an empty list. Verified on 192.168.1.143 (ZimaOS 1.6.2): the tab now lists live flows, IPv6 included, and an unknown L4 protocol renders as "PROTO-2" instead of being dropped. The failure path was forced by removing CAP_NET_ADMIN from the daemon via a temporary systemd drop-in: the UI showed "Could not read the connection table: ... ctnetlink: netlink: operation not permitted ...", the daemon logged it at ERROR, and the drop-in was removed afterwards. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1.
The Connections tab could never work on ZimaOS
ZFW read the connection table from
/proc/net/nf_conntrack, falling back toconntrack(8). A stock ZimaOS kernel is built with:and the image ships no
conntrackbinary. Both sources were therefore unavailable — on a host that was tracking 877 flows when measured. @benhaube's own diagnosis in the issue was right on both counts (and ZFW never usedlsmod, so that half of his hypothesis was moot).ZFW now reads ctnetlink (
NETLINK_NETFILTER/IPCTNL_MSG_CT_GETwithNLM_F_DUMP) in pure Go viasyscall— no new dependency,go.modstill has none. OnlyCTA_TUPLE_ORIGis decoded, matching the original-direction-only rule of the existing parser./procandconntrack(8)stay as fall-throughs for hosts that have them.Attribute headers are host byte order; payloads (ports,
CTA_TIMEOUT) are network order. Each is decoded explicitly, and a test fails if a port is ever read little-endian — that mix-up is the classic ctnetlink bug.The silent 200 that misdirected the report
The handler did
if err != nil { entries = []Entry{} }and answered HTTP 200, so "cannot read the table" was indistinguishable from "no connections". The UI then said the kernel conntrack module was unavailable — on a host where it was running fine. That message is what sent the reporter looking atlsmodandmodules.builtin.Now: an unreadable table is a 503 naming every source that failed, logged at
ERROR, and the UI prints that reason. The stale panel hint is gone.Tests
testdata/ctnetlink_dump.binholds 40 real ctnetlink messages captured from a live ZimaOS 1.6.2 host (kernel 6.18.9). Only the IP addresses were rewritten in place, to documentation ranges — every length, offset and nesting is the kernel's own, so the parser is tested against reality rather than against my idea of the wire format.Covered: TCP + UDP + IPv6 records, TCP state mapping, big-endian ports, truncation at every byte offset (must not panic), the dump limit across buffers,
NLMSG_DONE, andNLMSG_ERROR(EPERM) surfacing as an error rather than an empty list. Each new guard was confirmed to fail against the pre-fix code first.Live verification (192.168.1.143, ZimaOS 1.6.2)
SYN_SENT,TIME_WAIT,ESTABLISHED,CLOSE_WAIT, stateless UDP, IPv6 (fe80::…:5353 → ff02::fb), and an unknown L4 protocol rendered asPROTO-2instead of being dropped.CAP_NET_ADMINwas removed from the daemon via a temporary systemd drop-in. The UI showed the reason, the daemon logged it atERROR, HTTP was 503. The drop-in was then removed and the daemon restored.🤖 Generated with Claude Code