Files
pf-linux-port/tools/UPSTREAM.md
T
lgcosta 5323ee2c71 M2: live netfilter data path (pf_test per direction), pfattach bring-up, NET_LOCK reentrancy fix, static pfctl
- 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
2026-11-11 15:20:01 -03:00

127 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 */`)
1. **parse.y** (declarations section): `expand_label_addr`/`expand_label`
prototypes: address-family parameter `u_int8_t` → `sa_family_t`.
Linux `sa_family_t` is `unsigned short` (OpenBSD: `unsigned char`); the
matching definitions in the same file already use `sa_family_t`.
2. **pfctl_parser.c** (`ifa_load`): dropped two
`ifa_broadaddr->sa_len != 0` / `ifa_dstaddr->sa_len != 0` guards —
glibc `struct sockaddr` has no `sa_len`; `getifaddrs(3)` only fills those
members when a broadcast/destination address actually exists.
3. **pfctl_parser.h**: `geticmptypebynumber`, `geticmptypebyname`,
`geticmpcodebynumber`, `geticmpcodebyname` prototype af parameters
`u_int8_t` → `sa_family_t` (same reason as #1).
4. **pfctl_parser.c**: `print_fromto` forward prototype, af parameter
`u_int8_t` → `sa_family_t`.
5. **pf_print_state.c** (`print_name`): removed `sin_len`/`sin6_len`
assignments; added local `socklen_t slen` set to `sizeof(*sin|*sin6)` and
passed to `getnameinfo(3)` (no `ss_len` on Linux).
6. **pfctl_table.c** (`print_ad` reverse-DNS): identical `sa_len` removal
pattern as #5.
7. **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 `-f` load 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_key` is kernel-only in 8.0 (moved to
`<net/pfvar_priv.h>`, which userland MUST NOT include). The table
therefore probes its userland mirror `struct pf_state_key_cmp`
("keep synced with struct pf_state_key, used in RB_FIND") plus the
wire-format `struct pfsync_state_key` / `struct pfsync_state` used by
`DIOCGETSTATE` payloads. The kernel-side comparison of the real
`struct pf_state_key` is the integrator's job, same as for all
`-D_KERNEL` layout checks.
## Known warnings (intentional, benign)
* `-Wformat=` (×51): `%lld`/`%llu` vs `long` printf 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_state` is `__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. `strrchr` result into non-const, GCC's path-insensitive
truncation heuristics). Non-fatal, semantics unchanged.
## Out of scope / pending
* `pdivert` and `pflogd` — intentionally NOT ported here; owned by the
integrator. (No skeleton stubs are shipped.)
* Kernel-side ABI confirmation (same table compiled under `-D_KERNEL` with
the module compat headers) — integrator step; `out/abi-tools.tbl` /
`out/abi-module.tbl` + `out/abitest` md5 line are the reference inputs.
* Runtime paths (`-s`, `-F`, live `-f` load) require `/dev/pf` from pf.ko and
were exercised only through the `-n` (offline) code path here.