mirror of
https://github.com/lgcosta/pf-linux-port.git
synced 2026-11-12 20:15:36 -03:00
- 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
127 lines
9.4 KiB
Markdown
127 lines
9.4 KiB
Markdown
# 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.
|