Appendix 6.B PNP ABI Notes
§6.A is generated from pkm/uapi/pkm/pnp.h and holds only what a
compiler can measure. This appendix holds the rest.
6.B.1 Stability #
The ABI is experimental while PNP grows (PEI-598): no stability
promise until the design ships. peios_pnp_status.abi carries
PEIOS_PNP_ABI_VERSION; a consumer checks it before trusting any other
field or record layout. Version 2 (the machinery slice) added the
event's reject_kind, the store confessions and counter_cells and
reporting_level in the status, and the counters ioctl. Version 3 (the
Flow layer) added the LOCAL_OUT seat and Flow layer ids, the
REJUDGED event flag, the Flow and refusal counters in the status, and
the flows ioctl. pnpd and the kernel ship together on the experimental
edition, so the check is a guard, not a negotiation.
6.B.2 /dev/peios-pnp #
A misc device, mode 0600, root-only by ownership.
| Operation | Semantics | Errors |
|---|---|---|
open() | One reader at a time. | EBUSY — already open elsewhere |
read(buf, len) | Returns whole struct peios_pnp_event records only — never a partial one — up to 64 per call, oldest first, consuming them. Blocks on an empty ring unless O_NONBLOCK. | EINVAL — len smaller than one record; EAGAIN — empty and non-blocking; EINTR; ENOMEM; EFAULT |
poll() | POLLIN | POLLRDNORM when at least one event waits. | — |
ioctl(PEIOS_PNP_IOC_STATUS, struct peios_pnp_status *) | Fills the status snapshot. Cumulative counters since boot. | EFAULT |
ioctl(PEIOS_PNP_IOC_COUNTERS, struct peios_pnp_counters_query *) | buf/buf_len describe a user buffer of struct peios_pnp_counter_rec; on return count is how many were written and total how many cells exist. A short buffer is not an error — the two numbers disagree. Best-effort snapshot: cells may change between records. | EFAULT; ENOMEM |
ioctl(PEIOS_PNP_IOC_FLOWS, struct peios_pnp_flows_query *) | Same contract over struct peios_pnp_flow_rec: count written, total live flows the walk saw. Records are copied out between hash buckets, so the dump is a best-effort picture of a table that changes under it. | EFAULT; ENOMEM |
| other ioctls | — | ENOTTY |
Sequence numbers are monotonic per boot. A gap between consecutive
records read is exactly the number of events the ring overwrote while
the reader was away; events_dropped in the status is the running
total.
6.B.3 Event fields #
attributedis the winning rule's path relative to its layer key, UTF-8, NUL-terminated, truncated toPEIOS_PNP_EV_ATTR_LEN− 1 bytes. Two reserved values:backstop(nothing yielded) andfail-closed(evaluation failed).effectspacks the effect counts the evaluation yielded —tags | counts << 8 | reports << 16 | prompts << 24, each saturating at 255. What the stores then applied is in the status confessions, not in the event.reject_kindis meaningful only whenverdictisPEIOS_PNP_EV_VERDICT_REJECT; a degraded reject (flags & PEIOS_PNP_EV_F_REJECT_DEGRADED) still carries the kind the rule chose.PEIOS_PNP_EV_F_REJUDGEDmarks aFlow-layer evaluation that replaced a stale sentence (policy change or time edge). A packet answered by a current sentence produces no event at all.layer2 isFlow;seat4 isLOCAL_OUT. AFlowevent'sdirectionis the endpoint judged: the originator's side for a normal flow, each seat's own for a loopback flow's two judgments.- Addresses: the first 4 bytes when
addr_familyis 4, all 16 when 6, undefined when 0. Ports are 0 when the fact was absent — checkprotocol. flow_state0 means the fact was absent (the ingress seat), not that the flow was untracked; untracked is 5.
6.B.4 Counter records #
nameis the stream name (a rule'sCOUNT(Name)), NUL-terminated;hashis its FNV-1a identity as the kernel keys tables.keyspecsays which ofsrc_addr,dst_addr,ifindexare meaningful for this cell's key; the others are zero.familyis 4, 6, or 0 when no address fact is keyed.window_secs[i]/window_value[i]fori < n_windowsare the table's windows and this cell's current value in each;totalis cumulative since the cell was created (or migrated — see §6.6);last_secsisCLOCK_REALTIMEseconds of the last write, for staleness.- The dump lists every table's cells consecutively; group by
(name, keyspec)to reconstruct tables.
6.B.5 Flow records #
idis conntrack's own id for the entry (nf_ct_get_id()), stable for the flow's life and the key to use across dumps.src_addr/dst_addr, the ports and the ICMP fields are the original-direction tuple — the originator first. For ICMP,src_portcarries the echo id andicmp_type/icmp_codethe type and code;dst_portis 0.direction,ifindexandloopbackare meaningful only whenjudgedis 1: they were recorded at the Flow layer's first judgment. A flow withjudged == 0began under a permissive generation.- The sentences are parallel arrays indexed by slot (UAPI records hold
scalars only): slot 0 is the flow's sentence; slot 1 is only ever
filled for a loopback flow (its inbound endpoint). A slot with
sentence_generation == 0is empty.sentence_expires_at0 means never.sentence_rule_hashis FNV-1a-64 (offset0xcbf29ce484222325, prime0x100000001b3) of the attributing path relative to the layer key —backstopandfail-closedhash like any other path. packets/bytesare conntrack's accounting, original then reply; PNP enablesnf_conntrack_acctat init.timeout_secsis the entry's remaining lifetime as conntrack sees it;start_secsisCLOCK_REALTIMEseconds when conntrack created it.tag_hash/tag_valuehold up toPEIOS_PNP_FLOW_MAX_TAGS(8) present tags by name hash and value;n_tagsis the flow's total, so a value above 8 means some are not listed.
6.B.6 Bounds not in the header #
| Bound | Value |
|---|---|
| Event ring | 4096 records, overwrite-oldest |
Records per read() | 64 |
| Counter cells per table | 4096 (PEIOS_PNP_COUNTER_MAX_KEYS, kernel-internal) |
| Distinct tags per flow | 64 (PEIOS_PNP_TAG_MAX_PER_FLOW, kernel-internal) |
| Rule depth / rules per layer | 12 / 4096 (ingestion) |
| Longest counter window | 86 400 s |
| Sentences per flow | 2 (slot 1 only for loopback flows) |
| Flow records batched per copy-out | 32 (kernel-internal) |
6.B.7 Build configuration #
CONFIG_PEIOS_PNP (bool) depends on SECURITY_PKM, NETFILTER_INGRESS,
NETFILTER_EGRESS and NF_CONNTRACK=y — PNP is built in and reads flow
facts on the packet path, so conntrack must be too. CONFIG_PEIOS_PNP_KUNIT
builds the kernel-resident tests (pkm_kunit_pnp), defaulting to
SECURITY_PKM_KUNIT. The production fragment (build/config/pkm.fragment)
enables PNP and configures the nf_tables/xtables family out;
kernel/verify-kernel-config.sh asserts both.