Skip to content

Restructure PFUI for C rewrite and other DNS servers - #2

Merged
andylemin merged 2 commits into
masterfrom
restructure
Aug 31, 2026
Merged

Restructure PFUI for C rewrite and other DNS servers#2
andylemin merged 2 commits into
masterfrom
restructure

Conversation

@andylemin

Copy link
Copy Markdown
Owner

Restructure PFUI for C rewrite and other DNS servers

Andy Lemin added 2 commits September 1, 2026 00:01
Everything lived in the repository root, which conflated three things with
different lifecycles: the wire protocol, a resolver-side client, and a
firewall-side server. Supporting another DNS server or a second server
implementation meant adding more files to the same flat namespace.

  protocol/        specification, conformance vectors, Python reference
  client-unbound/  the Unbound pythonmod plugin and its rc.d, tools and docs
  server-python/   the PF daemon, its rc.d, and its server-only modules
  examples/        PF ruleset, which applies to any server implementation

Installers, README, INSTALL, DECISIONS and Debugging stay in the root, renamed
to install-client-unbound.sh and install-server-python.sh so it is clear which
component each installs once there is more than one of each.

Only the wire format is genuinely shared, so it is the only thing in protocol/.
Expiry, address validation and the PF ioctl layer are server-only and move with
the server. That split forced one code change: two directories cannot both
contribute to a regular `pfui` package, so the shared piece becomes a top-level
pfui_wire module and `pfui` now holds server internals alone. The installers
already placed the shared module beside each daemon, so the runtime import is
unchanged in kind; a root conftest.py mirrors that layout for the tests, so they
import it the way production does rather than merely equivalently.

The protocol gains a written specification and conformance vectors. The vectors
are the point: framing.tsv is language-neutral so an implementation with no JSON
parser can still be checked, which is what lets a second implementation exist
without drifting. Two of them documented a real semantic that had never been
stated - a peer closing before a complete header yields no message, whereas a
payload short of its declared length is an error.

Tests move to the component they exercise. All moves are git renames, so history
follows the files.
…d vectors

First piece of the C server, and deliberately the smallest useful one: the
length-prefixed framing from protocol/PROTOCOL.md, with no listener, no JSON, no
lz4, no Redis and no PF ioctl. src/pfui_wire.c hands back the payload as opaque
bytes, so the framing layer can be complete and correct on its own.

Its value right now is not the code but the second opinion: tests/test_vectors.c
reads protocol/vectors/framing.tsv, the same file the Python suite reads, so the
specification has two independent implementations checking it and neither can
drift quietly. Writing it immediately earned that keep - the C reader tripped
over a vector whose frame field was empty, because strtok collapses consecutive
tabs, so the vector format now forbids empty fields and uses "-" instead. A
format that only one implementation had ever parsed looked fine.

Builds with any C99 compiler under -Wall -Wextra -Werror -pedantic; make test
runs the vectors. CI builds it on every push.

server-c/README.md lists what is still missing, in dependency order, and records
that server-python remains the reference while PROTOCOL.md is normative where
the two disagree.
@andylemin
andylemin merged commit c683ff5 into master Aug 31, 2026
8 checks passed
@andylemin
andylemin deleted the restructure branch September 3, 2026 10:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant