- pfk_hooks: PRE→PF_IN, LOCAL_OUT/POST→PF_OUT v4+v6; replacement/buffer handling with NF_STOLEN + re-injection (documented divergence: re-entry) - pfk_mod: pf_status_init/pfattach + pfi attach via pfk_if event callback - pfk_kern: NET_LOCK holder reentrancy (fix deadlock when notifier fires under the write lock — observed as full-VM network hang) - tools: static pfctl link for musl VMs
9.4 KiB
PFLINUX — OpenBSD 8.0 pfctl on Linux: upstream provenance and edits
Sources: _vendor/upstream/sbin/pfctl/ (OpenBSD 8.0-current, see
_vendor/upstream/IDS.txt), ABI header: module/include/net/pfvar.h
(v1.548), ioctl encoding: module/include/pfk.h.
This document lists every deviation from the pristine vendor sources, the
compat layer added under tools/, and the warnings emitted by the build.
Build-time header copies (Makefile rules, never hand-edited)
| tools/include/… | copied from |
|---|---|
net/pfvar.h |
module/include/net/pfvar.h (contract) |
sys/tree.h, sys/queue.h |
_vendor/upstream/sys/sys/{tree,queue}.h |
net/radix.h |
_vendor/upstream/sys/net/radix.h |
net/hfsc.h, net/fq_codel.h |
_vendor/upstream/sys/net/{hfsc,fq_codel}.h |
make abi compiles abitest.c once against each header set and diffs the
tables + MD5, proving the copy is in lockstep with module/include.
Compat headers (hand-written, tools/include/)
| file | purpose |
|---|---|
sys/ioctl.h |
glibc <sys/ioctl.h> via #include_next, then _IO/_IOW/_IOR/_IOWR redefined to the PFKXIOC (OpenBSD ioccom.h) encoding copied verbatim from module/include/pfk.h (NRBITS 8, TYPEBITS 8, SIZEBITS 13, DIRBITS 2, NRSHIFT 0, TYPESHIFT 8, SIZESHIFT 16, DIRSHIFT 30, NONE 0, WRITE 1, READ 2). This makes every DIOC* number in pfvar.h identical to what pf.ko decodes. |
pflinux_compat.h |
force-included (-include): <sys/types.h>, <strings.h> (bzero/bcopy), offsetof, OpenBSD cdefs fallbacks __dead/__printflike/__packed/__unused/__inline, HTONL/NTOL/HTONS/NOSH (assignment-style, <sys/param.h>), AF_LINK → AF_PACKET (parse.y uses it without <net/if_dl.h>), prototypes for strtonum, fgetln, freezero, uid_from_user, gid_from_group, pledge, unveil. |
sys/sysctl.h |
CTL_*/KERN_MAXCLUSTERS(67)/HW_PHYSMEM64(19) constants (OpenBSD values) + sysctl() prototype (glibc dropped <sys/sysctl.h>). |
sys/syslimits.h |
→ <limits.h> (PATH_MAX etc. for pfvar.h). |
sys/_null.h |
OpenBSD NULL header needed by vendored queue.h/tree.h. |
sys/refcnt.h |
userland struct refcnt { unsigned int rc; } — same size as the module kernel compat (refcount_t = one int); referenced by struct pf_anchor only, never crosses ioctl. |
sys/timeout.h |
minimal struct timeout — pfvar.h embeds it in struct pf_pktdelay, which is kernel-internal (userland state payloads use struct pfsync_state). |
net/route.h |
userland subset pfvar.h/parse.y need: RTLABEL_LEN 32, RT_TABLEID_MAX 32 (matches module/include/pfk.h, not OpenBSD's 255), NET_RT_* numbers, struct rt_tableinfo (verbatim from obbsd route.h), forward struct rtentry. AF_ROUTE/PF_ROUTE come from glibc <sys/socket.h> (both are 17 there, same as OpenBSD). |
net/if.h |
glibc header via #include_next + OpenBSD interface-group ABI (struct ifg_req, struct ifgroupreq, SIOCGIFGMEMB with the OpenBSD number, IFQ_NQUEUES/IFQ_MAXPRIO). Linux has no interface groups: the ioctl fails ENOTTY and group names degrade to "no such group", like an OpenBSD box missing the group. |
net/if_dl.h |
AF_LINK ≡ AF_PACKET; struct sockaddr_dl laid out to mirror Linux sockaddr_ll so pfctl_parser.c's sdl_index cast reads sll_ifindex (offset 4). AF_LINK is userland-internal (iftab bookkeeping); it never enters ioctl payloads. |
netinet/tcp_fsm.h |
TCPS_* states + TCP_NSTATES 11 (obbsd values); tcpstates[] extern (defined in compat). |
netinet/ip_icmp.h, netinet/icmp6.h, netinet/ip.h |
#include_next + the ICMP*/ICMP6_/MLD_/ND_/IPTOS_DSCP_CS constants glibc lacks (verbatim obbsd values). |
md5.h |
copy of _vendor/upstream/sys/crypto/md5.h minus the kernel-only __bounded__ annotations. |
Compat sources (tools/compat-user/)
| file | purpose |
|---|---|
sysctl.c |
sysctl(3) emulation for exactly the three MIBs pfctl uses: HW_PHYSMEM64 (sysconf), KERN_MAXCLUSTERS (physmem/2048, the OpenBSD RAM-based sizing), NET_RT_TABLE (struct rt_tableinfo, always "exists" for ids ≤ RT_TABLEID_MAX — mirrors module compat/pfk_rtable.c: Linux has no per-rtableid FIBs, rtable_exists() accepts every id). |
strtonum.c |
BSD strtonum(3) (glibc has none). |
md5.c |
vendor _vendor/upstream/sys/crypto/md5.c, only the three include lines replaced (sys/param.h,sys/systm.h,crypto/md5.h → sys/types.h,endian.h,string.h,md5.h); algorithm byte-identical. |
rbtree.c |
OpenBSD's out-of-line generic RB tree (_rb_insert/_rb_find/_rb_remove/_rb_min/_rb_next/...) that sys/tree.h's RBT_* macros call; OpenBSD ships it in libc, glibc has none. CLRS-based, verified by a 30k-op/12-seed fuzz harness (order, parent-links, bidirectional walks, find identity). |
bsd.c |
tcpstates[] (obbsd text), pledge/unveil no-op stubs (unused by 8.0 sources, kept for future syncs), fgetln() over getline() with OpenBSD *len semantics, freezero(), uid_from_user(), gid_from_group(). |
inet_net_pton.c |
BSD inet_net_pton() for AF_INET abbreviated CIDR ("10/8", classful defaults) + AF_INET6; glibc 2.43 declares it but exports no symbol. |
pfctl/pf_ruleset.c: not copied — the Makefile compiles
_vendor/upstream/sys/net/pf_ruleset.c unmodified into pfctl (its
!_KERNEL branch exists precisely "so we can lend code to pfctl", providing
pf_anchor_setup, pf_find_or_create_ruleset, pf_main_anchor, …).
Verified with cmp against vendor on every build (file read-only here).
Grammar toolchain
Only bison is needed: OpenBSD parse.y carries a hand-written yylex()
(flex unused). bison 3.8 accepts the OpenBSD yacc idioms used here —
prologue typedef struct {union{...} v; int lineno;} YYSTYPE plus
%type <v.member> dotted tags — with zero pre-processing. Duplicate
%token RTABLE/RDOMAIN lines produce warnings (upstream text).
Edits to the copied pfctl sources (all marked in-file with /* PFLINUX */)
- parse.y (declarations section):
expand_label_addr/expand_labelprototypes: address-family parameteru_int8_t→sa_family_t. Linuxsa_family_tisunsigned short(OpenBSD:unsigned char); the matching definitions in the same file already usesa_family_t. - pfctl_parser.c (
ifa_load): dropped twoifa_broadaddr->sa_len != 0/ifa_dstaddr->sa_len != 0guards — glibcstruct sockaddrhas nosa_len;getifaddrs(3)only fills those members when a broadcast/destination address actually exists. - pfctl_parser.h:
geticmptypebynumber,geticmptypebyname,geticmpcodebynumber,geticmpcodebynameprototype af parametersu_int8_t→sa_family_t(same reason as #1). - pfctl_parser.c:
print_fromtoforward prototype, af parameteru_int8_t→sa_family_t. - pf_print_state.c (
print_name): removedsin_len/sin6_lenassignments; added localsocklen_t slenset tosizeof(*sin|*sin6)and passed togetnameinfo(3)(noss_lenon Linux). - pfctl_table.c (
print_adreverse-DNS): identicalsa_lenremoval pattern as #5. - pfctl_osfp.c (
pfctl_file_fingerprints): a missing/etc/pf.os(ENOENT) returns success instead of warn+fail — OpenBSD ships the file in base; this port has no fingerprint packaging yet. Other open errors still fatal. (Without this, every-fload exits 1 on stock Linux.)
No other lines differ from vendor; cmp each file against
_vendor/upstream/sbin/pfctl/ to audit (diff -u shows only the sites above).
ABI table notes (tools/abitest/abitest.c)
- The contract's
pf_state_keyis kernel-only in 8.0 (moved to<net/pfvar_priv.h>, which userland MUST NOT include). The table therefore probes its userland mirrorstruct pf_state_key_cmp("keep synced with struct pf_state_key, used in RB_FIND") plus the wire-formatstruct pfsync_state_key/struct pfsync_stateused byDIOCGETSTATEpayloads. The kernel-side comparison of the realstruct pf_state_keyis the integrator's job, same as for all-D_KERNELlayout checks.
Known warnings (intentional, benign)
-Wformat=(×51):%lld/%lluvslongprintf conversions. OpenBSD prints 64-bit values with%ll*; LP64 glibc types differ in spelling only. Output text is identical.-Waddress-of-packed-member(×6):struct pfsync_stateis__packed(wire format shared with pf.ko); pf_print_state.c takes addresses of its members. Access is alignment-safe on x86-64 (no AVX requirement).-Wother(×6): bison "symbol RTABLE/RDOMAIN redeclared" — upstream parse.y declares these tokens twice.-Wpointer-sign(×15) /-Wformat-truncation(×10) /-Wdiscarded-qualifiers(×2): snprintf/char*signedness and const-preserving idioms from OpenBSD sources (e.g.strrchrresult into non-const, GCC's path-insensitive truncation heuristics). Non-fatal, semantics unchanged.
Out of scope / pending
pdivertandpflogd— intentionally NOT ported here; owned by the integrator. (No skeleton stubs are shipped.)- Kernel-side ABI confirmation (same table compiled under
-D_KERNELwith the module compat headers) — integrator step;out/abi-tools.tbl/out/abi-module.tbl+out/abitestmd5 line are the reference inputs. - Runtime paths (
-s,-F, live-fload) require/dev/pffrom pf.ko and were exercised only through the-n(offline) code path here.