# Appendix 6.A NTFE ABI Reference

_Peios / Peios technical reference / PKM / NTFE_

> Every NTFE ioctl number, event and status structure layout, counter record layout and constant, generated from the uapi header and measured by compilation.

Every name, value, offset and size in this appendix is generated from
`pkm/uapi/pkm/ntfe.h` by `pkm/tools/gen-ntfe-abi.py`, with struct
layouts measured by compiling a probe against the real header.
Regenerate it whenever the ABI changes; do not edit it by hand. The
names here are the ones a program actually compiles
against. [*abi.ntfe-generated-from-source]

What a compiler cannot measure -- the device's read and poll
semantics, what each ioctl expects, the error vocabulary, and the
bounds that are not in the header -- is in the notes appendix, §6.B,
which this generator does not touch.

## 6.A.1 Ioctl requests [*abi.ioctl-numbers]

Request numbers on `/dev/peios-ntfe`, packed as `<linux/ioctl.h>`
packs them (direction, argument size, type `'N'`, number).

| Constant | Value | Definition |
|---|---|---|
| `PEIOS_NTFE_IOC_STATUS` | `0x81704E01` | `_IOR(PEIOS_NTFE_IOC_TYPE, PEIOS_NTFE_IOC_STATUS_NR, struct peios_ntfe_status)` |
| `PEIOS_NTFE_IOC_COUNTERS` | `0xC0184E02` | `_IOWR(PEIOS_NTFE_IOC_TYPE, PEIOS_NTFE_IOC_COUNTERS_NR, struct peios_ntfe_counters_query)` |
| `PEIOS_NTFE_IOC_FLOWS` | `0xC0184E03` | `_IOWR(PEIOS_NTFE_IOC_TYPE, PEIOS_NTFE_IOC_FLOWS_NR, struct peios_ntfe_flows_query)` |
| `PEIOS_NTFE_IOC_LISTENERS` | `0xC0184E04` | `_IOWR(PEIOS_NTFE_IOC_TYPE, PEIOS_NTFE_IOC_LISTENERS_NR, struct peios_ntfe_listeners_query)` |

## 6.A.2 Structure layouts

Offsets and sizes are measured, not declared.

### 6.A.2.1 `struct peios_ntfe_event` [*abi.struct-peios-ntfe-event]

Total size 456 bytes.

| Offset | Size | Type | Field |
|---|---|---|---|
| 0 | 8 | `__u64` | `seq` |
| 8 | 8 | `__u64` | `t_ns` |
| 16 | 1 | `__u8` | `seat` |
| 17 | 1 | `__u8` | `layer` |
| 18 | 1 | `__u8` | `verdict` |
| 19 | 1 | `__u8` | `flags` |
| 20 | 1 | `__u8` | `direction` |
| 21 | 1 | `__u8` | `addr_family` |
| 22 | 1 | `__u8` | `protocol` |
| 23 | 1 | `__u8` | `flow_state` |
| 24 | 4 | `__u32` | `ifindex` |
| 28 | 2 | `__u16` | `src_port` |
| 30 | 2 | `__u16` | `dst_port` |
| 32 | 2 | `__u16` | `ether_type` |
| 34 | 1 | `__u8` | `reject_kind` |
| 35 | 1 | `__u8` | `_pad0` |
| 36 | 16 | `__u8[16]` | `src_addr` |
| 52 | 16 | `__u8[16]` | `dst_addr` |
| 68 | 4 | `__u32` | `length` |
| 72 | 4 | `__u32` | `effects` |
| 76 | 96 | `__u8[PEIOS_NTFE_EV_ATTR_LEN]` | `attributed` |
| 172 | 4 | `__u32` | `_pad1` |
| 176 | 1 | `__u8` | `local_kind` |
| 177 | 1 | `__u8` | `remote_kind` |
| 178 | 1 | `__u8` | `local_unresolved` |
| 179 | 1 | `__u8` | `remote_unresolved` |
| 180 | 4 | `__s32` | `local_pid` |
| 184 | 4 | `__s32` | `remote_pid` |
| 188 | 16 | `__u8[PEIOS_NTFE_GUID_LEN]` | `local_guid` |
| 204 | 16 | `__u8[PEIOS_NTFE_GUID_LEN]` | `remote_guid` |
| 220 | 16 | `__u8[PEIOS_NTFE_COMM_LEN]` | `local_comm` |
| 236 | 16 | `__u8[PEIOS_NTFE_COMM_LEN]` | `remote_comm` |
| 252 | 68 | `__u8[PEIOS_NTFE_SID_LEN]` | `local_user` |
| 320 | 68 | `__u8[PEIOS_NTFE_SID_LEN]` | `remote_user` |
| 388 | 32 | `__u8[PEIOS_NTFE_SERVICE_SID_LEN]` | `local_service` |
| 420 | 32 | `__u8[PEIOS_NTFE_SERVICE_SID_LEN]` | `remote_service` |
| 452 | 4 | `__u32` | `_pad2` |

### 6.A.2.2 `struct peios_ntfe_status` [*abi.struct-peios-ntfe-status]

Total size 368 bytes.

| Offset | Size | Type | Field |
|---|---|---|---|
| 0 | 8 | `__u64` | `abi` |
| 8 | 8 | `__u64` | `generation` |
| 16 | 8 | `__u64` | `enforcing` |
| 24 | 8 | `__u64` | `events_dropped` |
| 32 | 8 | `__u64` | `seen_ingress` |
| 40 | 8 | `__u64` | `seen_egress` |
| 48 | 8 | `__u64` | `seen_local_in` |
| 56 | 8 | `__u64` | `deferred` |
| 64 | 8 | `__u64` | `fallback_judged` |
| 72 | 8 | `__u64` | `parse_errors` |
| 80 | 8 | `__u64` | `judged` |
| 88 | 8 | `__u64` | `permissive` |
| 96 | 8 | `__u64` | `fail_closed` |
| 104 | 8 | `__u64` | `verdict_pass` |
| 112 | 8 | `__u64` | `verdict_drop` |
| 120 | 8 | `__u64` | `verdict_reject` |
| 128 | 8 | `__u64` | `reject_degraded` |
| 136 | 8 | `__u64` | `fx_tags` |
| 144 | 8 | `__u64` | `fx_counts` |
| 152 | 8 | `__u64` | `fx_reports` |
| 160 | 8 | `__u64` | `fx_prompts` |
| 168 | 8 | `__u64` | `last_ingest_error` |
| 176 | 8 | `__u64` | `last_ingest_t_ns` |
| 184 | 8 | `__u64` | `tag_writes` |
| 192 | 8 | `__u64` | `tag_untracked` |
| 200 | 8 | `__u64` | `tag_refused` |
| 208 | 8 | `__u64` | `count_writes` |
| 216 | 8 | `__u64` | `count_key_absent` |
| 224 | 8 | `__u64` | `count_refused` |
| 232 | 8 | `__u64` | `reports_emitted` |
| 240 | 8 | `__u64` | `counter_cells` |
| 248 | 8 | `__u64` | `reporting_level` |
| 256 | 8 | `__u64` | `seen_local_out` |
| 264 | 8 | `__u64` | `flow_judged` |
| 272 | 8 | `__u64` | `flow_cached` |
| 280 | 8 | `__u64` | `flow_rejudged` |
| 288 | 8 | `__u64` | `flow_expired` |
| 296 | 8 | `__u64` | `flow_uncached` |
| 304 | 8 | `__u64` | `refusals_emitted` |
| 312 | 8 | `__u64` | `refusals_bypassed` |
| 320 | 8 | `__u64` | `teardowns_emitted` |
| 328 | 8 | `__u64` | `identity_unresolved` |
| 336 | 8 | `__u64` | `changes_noted` |
| 344 | 8 | `__u64` | `changes_walked` |
| 352 | 8 | `__u64` | `contexts` |
| 360 | 8 | `__u64[1]` | `_reserved` |

### 6.A.2.3 `struct peios_ntfe_counter_rec` [*abi.struct-peios-ntfe-counter-rec]

Total size 232 bytes.

| Offset | Size | Type | Field |
|---|---|---|---|
| 0 | 64 | `__u8[PEIOS_NTFE_COUNTER_NAME_LEN]` | `name` |
| 64 | 8 | `__u64` | `hash` |
| 72 | 1 | `__u8` | `keyspec` |
| 73 | 1 | `__u8` | `family` |
| 74 | 2 | `__u8[2]` | `_pad0` |
| 76 | 4 | `__s32` | `ifindex` |
| 80 | 16 | `__u8[16]` | `src_addr` |
| 96 | 16 | `__u8[16]` | `dst_addr` |
| 112 | 8 | `__u64` | `total` |
| 120 | 8 | `__u64` | `last_secs` |
| 128 | 4 | `__u32` | `n_windows` |
| 132 | 4 | `__u32` | `_pad1` |
| 136 | 32 | `__u32[PEIOS_NTFE_COUNTER_MAX_WINDOWS]` | `window_secs` |
| 168 | 64 | `__u64[PEIOS_NTFE_COUNTER_MAX_WINDOWS]` | `window_value` |

### 6.A.2.4 `struct peios_ntfe_counters_query` [*abi.struct-peios-ntfe-counters-query]

Total size 24 bytes.

| Offset | Size | Type | Field |
|---|---|---|---|
| 0 | 8 | `__u64` | `buf` |
| 8 | 4 | `__u32` | `buf_len` |
| 12 | 4 | `__u32` | `count` |
| 16 | 4 | `__u32` | `total` |
| 20 | 4 | `__u32` | `_pad0` |

### 6.A.2.5 `struct peios_ntfe_flow_rec` [*abi.struct-peios-ntfe-flow-rec]

Total size 568 bytes.

| Offset | Size | Type | Field |
|---|---|---|---|
| 0 | 4 | `__u32` | `id` |
| 4 | 1 | `__u8` | `family` |
| 5 | 1 | `__u8` | `protocol` |
| 6 | 1 | `__u8` | `direction` |
| 7 | 1 | `__u8` | `loopback` |
| 8 | 1 | `__u8` | `seen_reply` |
| 9 | 1 | `__u8` | `assured` |
| 10 | 1 | `__u8` | `related` |
| 11 | 1 | `__u8` | `judged` |
| 12 | 4 | `__s32` | `ifindex` |
| 16 | 4 | `__u32` | `timeout_secs` |
| 20 | 16 | `__u8[16]` | `src_addr` |
| 36 | 16 | `__u8[16]` | `dst_addr` |
| 52 | 2 | `__u16` | `src_port` |
| 54 | 2 | `__u16` | `dst_port` |
| 56 | 1 | `__u8` | `icmp_type` |
| 57 | 1 | `__u8` | `icmp_code` |
| 58 | 1 | `__u8` | `n_tags` |
| 59 | 5 | `__u8[5]` | `_pad0` |
| 64 | 8 | `__u64` | `start_secs` |
| 72 | 16 | `__u64[2]` | `packets` |
| 88 | 16 | `__u64[2]` | `bytes` |
| 104 | 16 | `__u64[PEIOS_NTFE_FLOW_SENTENCES]` | `sentence_generation` |
| 120 | 16 | `__s64[PEIOS_NTFE_FLOW_SENTENCES]` | `sentence_expires_at` |
| 136 | 16 | `__u64[PEIOS_NTFE_FLOW_SENTENCES]` | `sentence_rule_hash` |
| 152 | 2 | `__u8[PEIOS_NTFE_FLOW_SENTENCES]` | `sentence_verdict` |
| 154 | 2 | `__u8[PEIOS_NTFE_FLOW_SENTENCES]` | `sentence_reject_kind` |
| 156 | 4 | `__u8[4]` | `_pad1` |
| 160 | 64 | `__u64[PEIOS_NTFE_FLOW_MAX_TAGS]` | `tag_hash` |
| 224 | 64 | `__u64[PEIOS_NTFE_FLOW_MAX_TAGS]` | `tag_value` |
| 288 | 2 | `__u8[PEIOS_NTFE_FLOW_SENTENCES]` | `owner_kind` |
| 290 | 2 | `__u8[PEIOS_NTFE_FLOW_SENTENCES]` | `owner_unresolved` |
| 292 | 4 | `__u8[4]` | `_pad2` |
| 296 | 8 | `__s32[PEIOS_NTFE_FLOW_SENTENCES]` | `owner_pid` |
| 304 | 32 | `__u8[32]` | `owner_guid` |
| 336 | 32 | `__u8[32]` | `owner_comm` |
| 368 | 136 | `__u8[136]` | `owner_user` |
| 504 | 64 | `__u8[64]` | `owner_service` |

### 6.A.2.6 `struct peios_ntfe_flows_query` [*abi.struct-peios-ntfe-flows-query]

Total size 24 bytes.

| Offset | Size | Type | Field |
|---|---|---|---|
| 0 | 8 | `__u64` | `buf` |
| 8 | 4 | `__u32` | `buf_len` |
| 12 | 4 | `__u32` | `count` |
| 16 | 4 | `__u32` | `total` |
| 20 | 4 | `__u32` | `_pad0` |

### 6.A.2.7 `struct peios_ntfe_listener_rec` [*abi.struct-peios-ntfe-listener-rec]

Total size 168 bytes.

| Offset | Size | Type | Field |
|---|---|---|---|
| 0 | 1 | `__u8` | `family` |
| 1 | 1 | `__u8` | `protocol` |
| 2 | 1 | `__u8` | `reuseport` |
| 3 | 1 | `__u8` | `connected` |
| 4 | 1 | `__u8` | `v6only` |
| 5 | 1 | `__u8` | `owner_kind` |
| 6 | 1 | `__u8` | `owner_unresolved` |
| 7 | 1 | `__u8` | `_pad0` |
| 8 | 2 | `__u16` | `port` |
| 10 | 2 | `__u16` | `_pad1` |
| 12 | 4 | `__s32` | `ifindex` |
| 16 | 16 | `__u8[16]` | `addr` |
| 32 | 4 | `__s32` | `owner_pid` |
| 36 | 16 | `__u8[PEIOS_NTFE_GUID_LEN]` | `owner_guid` |
| 52 | 16 | `__u8[PEIOS_NTFE_COMM_LEN]` | `owner_comm` |
| 68 | 68 | `__u8[PEIOS_NTFE_SID_LEN]` | `owner_user` |
| 136 | 32 | `__u8[PEIOS_NTFE_SERVICE_SID_LEN]` | `owner_service` |

### 6.A.2.8 `struct peios_ntfe_listeners_query` [*abi.struct-peios-ntfe-listeners-query]

Total size 24 bytes.

| Offset | Size | Type | Field |
|---|---|---|---|
| 0 | 8 | `__u64` | `buf` |
| 8 | 4 | `__u32` | `buf_len` |
| 12 | 4 | `__u32` | `count` |
| 16 | 4 | `__u32` | `total` |
| 20 | 4 | `__u32` | `_pad0` |

## 6.A.3 Constants

Grouped as the header groups them.

*NTFE — the Network Traffic Filtering Engine: the verdict event stream and engine status.*

The engine (net/ntfe) judges every traversal at its standing seats and
appends one event per evaluation to a bounded ring. /dev/peios-ntfe
(mode 0600; any number of openers, one reader of the stream at a time)
drains it: read() returns whole events only — never a partial record —
and blocks when the ring is empty unless O_NONBLOCK; poll() raises
POLLIN when events are waiting. A slow reader loses the OLDEST events,
and the loss is confessed in peios_ntfe_status.events_dropped (the
honesty rule: drops are counted, never silent).

Events are emitted for real evaluations (a published forest judged the
traversal) and for fail-closed drops; permissive traversals (a layer
with no forest — all of them at generation 0) emit nothing, because
there is no decision to attribute. Status tells that story instead:
generation 0 means "not enforcing", loudly.

This ABI is EXPERIMENTAL while NTFE grows: no stability promise until
the design ships (PEI-598). Check `abi` before trusting the rest.

| Constant | Value |
|---|---|
| `PEIOS_NTFE_ABI_VERSION` | `5` |

*Which standing seat judged the traversal.* [*abi.seat-values]

| Constant | Value |
|---|---|
| `PEIOS_NTFE_EV_SEAT_INGRESS` | `1` |
| `PEIOS_NTFE_EV_SEAT_EGRESS` | `2` |
| `PEIOS_NTFE_EV_SEAT_LOCAL_IN` | `3` |
| `PEIOS_NTFE_EV_SEAT_LOCAL_OUT` | `4` |

*Which rules layer.* [*abi.layer-values]

| Constant | Value |
|---|---|
| `PEIOS_NTFE_EV_LAYER_PACKET` | `0` |
| `PEIOS_NTFE_EV_LAYER_RAWPACKET` | `1` |
| `PEIOS_NTFE_EV_LAYER_FLOW` | `2` |

*The verdict, in strictness order.* [*abi.verdict-values]

| Constant | Value |
|---|---|
| `PEIOS_NTFE_EV_VERDICT_PASS` | `0` |
| `PEIOS_NTFE_EV_VERDICT_REJECT` | `1` |
| `PEIOS_NTFE_EV_VERDICT_DROP` | `2` |

*The story a REJECT told (meaningful iff verdict == REJECT).* [*abi.reject-kinds]

| Constant | Value | Notes |
|---|---|---|
| `PEIOS_NTFE_EV_REJECT_REFUSED` | `0` | RST / port-unreachable |
| `PEIOS_NTFE_EV_REJECT_PROHIBITED` | `1` | admin-prohibited |

*Traversal direction.* [*abi.direction-values]

| Constant | Value |
|---|---|
| `PEIOS_NTFE_EV_DIR_IN` | `0` |
| `PEIOS_NTFE_EV_DIR_OUT` | `1` |

*Flow state as the snapshot carried it (0 = the fact was absent).* [*abi.flow-state-values]

| Constant | Value |
|---|---|
| `PEIOS_NTFE_EV_FLOW_ABSENT` | `0` |
| `PEIOS_NTFE_EV_FLOW_NEW` | `1` |
| `PEIOS_NTFE_EV_FLOW_ESTABLISHED` | `2` |
| `PEIOS_NTFE_EV_FLOW_RELATED` | `3` |
| `PEIOS_NTFE_EV_FLOW_INVALID` | `4` |
| `PEIOS_NTFE_EV_FLOW_UNTRACKED` | `5` |

*Event flags.* [*abi.event-flags]

| Constant | Value | Notes |
|---|---|---|
| `PEIOS_NTFE_EV_F_BACKSTOP` | `0x01` | nothing yielded; DROP |
| `PEIOS_NTFE_EV_F_FAIL_CLOSED` | `0x02` | evaluation failed; DROP |
| `PEIOS_NTFE_EV_F_REJECT_DEGRADED` | `0x04` | REJECT emitted as DROP |
| `PEIOS_NTFE_EV_F_REJUDGED` | `0x08` | Flow: a stale sentence re-judged |
| `PEIOS_NTFE_EV_F_IDENTITY_UNRESOLVED` | `0x10` | Flow: an endpoint could not be attributed |

*What stood at an endpoint: the Flow layer's Local / Remote facts (ABI 4).*

| Constant | Value | Notes |
|---|---|---|
| `PEIOS_NTFE_EV_LOCAL_ABSENT` | `0` | not a Flow event / not local |
| `PEIOS_NTFE_EV_LOCAL_PROGRAM` | `1` | a process's socket |
| `PEIOS_NTFE_EV_LOCAL_KERNEL` | `2` | the stack itself |
| `PEIOS_NTFE_EV_LOCAL_SHARED` | `3` | inbound multicast / broadcast |
| `PEIOS_NTFE_EV_LOCAL_NONE` | `4` | nothing receives it |
| `PEIOS_NTFE_EV_ATTR_LEN` | `96` |  |

A SID's binary form: revision, sub-authority count, a 48-bit authority,
up to 15 sub-authorities. Self-sized by its count byte; all zero =
absent.

| Constant | Value |
|---|---|
| `PEIOS_NTFE_SID_LEN` | `68` |

*A per-service SID (S-1-5-80 + five sub-authorities) is exactly this.*

| Constant | Value |
|---|---|
| `PEIOS_NTFE_SERVICE_SID_LEN` | `32` |
| `PEIOS_NTFE_COMM_LEN` | `16` |
| `PEIOS_NTFE_GUID_LEN` | `16` |

One counter cell, as the counters dump reports it: the stream and key-
spec of its table, the key it holds (only the facts the key-spec names
are meaningful; the rest are zero), the cumulative total, and the value
of every window the table answers.

| Constant | Value |
|---|---|
| `PEIOS_NTFE_COUNTER_NAME_LEN` | `64` |
| `PEIOS_NTFE_COUNTER_MAX_WINDOWS` | `8` |

*Key-spec bits.* [*abi.keyspec-bits]

| Constant | Value |
|---|---|
| `PEIOS_NTFE_KEY_SRC_ADDR` | `0x01` |
| `PEIOS_NTFE_KEY_DST_ADDR` | `0x02` |
| `PEIOS_NTFE_KEY_INTERFACE` | `0x04` |

One live flow, as the flows dump reports it (ABI 3): conntrack's view of
the flow (original-direction tuple, state, remaining lifetime,
accounting), NTFE's extension (start time, the interface and direction
at first judgment, the sentences, the tags). Tags are reported by hash;
the policy names them.

| Constant | Value |
|---|---|
| `PEIOS_NTFE_FLOW_MAX_TAGS` | `8` |
| `PEIOS_NTFE_FLOW_SENTENCES` | `2` |

The listeners dump: fills `buf` with as many records as fit; `count` is
how many were written, `total` how many sockets the walk saw.

| Constant | Value |
|---|---|
| `PEIOS_NTFE_IOC_TYPE` | `0x0000004E` |
| `PEIOS_NTFE_IOC_STATUS_NR` | `0x00000001` |
| `PEIOS_NTFE_IOC_COUNTERS_NR` | `0x00000002` |
| `PEIOS_NTFE_IOC_FLOWS_NR` | `0x00000003` |
| `PEIOS_NTFE_IOC_LISTENERS_NR` | `0x00000004` |
