Skip to content

Commit 13b4181

Browse files
committed
feat(stack-coexist): auto dual-stack so one listen serves F-Stack and kernel (FF_KERNEL_COEXIST)
When FF_KERNEL_COEXIST is built and [stack] kernel_coexist=1, a plain ff_socket now creates an F-Stack socket plus a paired host kernel socket (native fd map in ff_host_interface.c); ff_bind/ff_listen/ff_close/ff_connect drive both stacks, ff_epoll_ctl registers a dual fd on both the kqueue and the host epoll, ff_epoll_wait merges them, and ff_accept returns a single-stack connection by source. SOCK_KERNEL/SOCK_FSTACK still force a single stack; macro-off or coexist-off stays byte-identical (nm: 0 coexist symbols). Adds native fd-map unit test, a dualstack demo mode, and revises kernel_event_support_spec to v6. Verified on hardware: one listen(80) answered 200 via both 9.134.214.176 (F-Stack/DPDK) and 127.0.0.1 (kernel).
1 parent ba14858 commit 13b4181

30 files changed

Lines changed: 1304 additions & 861 deletions
Lines changed: 50 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,85 @@
1-
# 00 Overview: F-Stack User-Space Stack + Local Kernel Stack COEXISTENCE (compile-macro gated + per-fd marker selection + unified events)
1+
# 00 Overview: F-Stack User-Space Stack + Local Kernel Stack AUTOMATIC DUAL-STACK COEXISTENCE (compile-macro gated + default dual-stack + marker single-stack override + unified events)
22

33
> **Document ID**: SPEC-KE-00
4-
> **Version**: v5 (compile-macro gating: `FF_KERNEL_COEXIST` off by default + runtime `kernel_coexist` dual-layer switch)
4+
> **Version**: v6 (**native automatic dual-stack coexistence** paradigm: with no marker, one `ff_socket` builds BOTH the F-Stack and kernel stacks, dual-drives both, unified events; markers override to single-stack, compatible with v5; still gated by the `FF_KERNEL_COEXIST` compile macro + runtime `kernel_coexist` dual-layer switch)
55
> **Date**: 2026-06-17
6-
> **Status**: Drafting
6+
> **Status**: Drafting (v6 design)
77
> **Scope**: Navigation, terminology, and scope statement for the spec in this directory
88
99
---
1010

11-
## 0. v5 Compile-Macro Gating Background (must read)
11+
## 0. v6 Background: from "per-fd either/or" to "automatic dual-stack" (must read)
1212

13-
v4 already landed coexistence (app ON F-Stack + per-fd `SOCK_KERNEL` to the kernel stack + unified events). But all v4 coexistence code in `lib/` is **compiled unconditionally**, so even deployments not using coexistence link it in and cannot guarantee byte-for-byte zero regression vs. upstream F-Stack.
13+
v5 (commit ba148589d) already gated all `lib/` coexistence code under the compile macro `FF_KERNEL_COEXIST` (`lib/Makefile` off by default), forming a **compile-time + runtime dual-layer switch**, and measured byte-for-byte zero regression when off and per-fd coexistence usable when on (real-machine perf in `10`). The v5 semantics are **per-fd either/or**:
1414

15-
**v5 new requirement**: gate ALL `lib/` coexistence code under a single compile macro `FF_KERNEL_COEXIST`, **commented off by default in `lib/Makefile`**, forming a **compile-time + runtime dual-layer switch**:
15+
- `ff_socket(type|SOCK_KERNEL)` → builds **only** a kernel fd, returns `ff_kernel_fd_encode(host_fd)` (≥`FF_KERNEL_FD_BASE`);
16+
- otherwise → builds **only** an F-Stack fd (raw fd).
1617

17-
- **Compile-time (`FF_KERNEL_COEXIST`)**: undefined → all coexistence code (managed kernel-fd bridge, fd discrimination, `ff_epoll` merge, `ff_socket` kernel branch, per-`ff_*` routing, config `kernel_coexist`, `SOCK_FSTACK/SOCK_KERNEL` macros) is **not compiled**; `libfstack.a` is **byte-for-byte zero regression** vs. upstream. Defined (`make FF_KERNEL_COEXIST=1` or uncomment) → compiled in.
18-
- **Runtime (config `kernel_coexist`)**: **only when the macro is enabled**, `config.ini [stack] kernel_coexist=1` actually enables per-fd `SOCK_KERNEL` to use the kernel stack; default `=0` stays per-fd F-Stack.
19-
- **Opt-in impact**: `ff_api.h` `SOCK_FSTACK`/`SOCK_KERNEL` macros are also wrapped by `FF_KERNEL_COEXIST`, so a consumer (APP) must likewise define the macro to see these markers — the reasonable semantics of "off by default, explicitly enabled".
18+
**v6 upgrades the default semantics to "automatic dual-stack"**: when `FF_KERNEL_COEXIST` is compiled in **and** `config.ini [stack] kernel_coexist=1`
2019

21-
**The correct paradigm**: within **one F-Stack application process**, business connections use the **F-Stack user-space stack (DPDK + FreeBSD)**, while fds carrying the `SOCK_KERNEL` marker use the **host Linux kernel stack**, and the two **coexist in a single event loop**, gated by the compile macro `FF_KERNEL_COEXIST` (off by default). This is exactly what F-Stack's two existing implementations do:
22-
- the `FF_KERNEL_EVENT` compile mode of `adapter/syscall` (hook/LD_PRELOAD);
23-
- nginx's `kernel_network_stack` config switch.
20+
- **Default (no marker) = dual-stack**: one `ff_socket` builds BOTH an F-Stack fd and a kernel host fd, registers `ff_native_fd_map[fstack_fd]=host_fd`, and **returns the F-Stack raw fd**; `ff_bind/ff_listen/ff_close/ff_connect` etc. **dual-drive both stacks** on that dual-stack fd — **one `listen(80)` listens on both the F-Stack (DPDK) and Linux kernel stacks simultaneously**. Local `curl 127.0.0.1:80` and remote access to `:80` via the DPDK NIC are both reachable, with no marker required.
21+
- **Marker single-stack override (compatible with v5)**: `type|SOCK_KERNEL` = kernel only; `type|SOCK_FSTACK` = F-Stack only.
22+
- **Zero regression**: macro off **or** `kernel_coexist=0` **or** `SOCK_FSTACK` → byte-for-byte zero regression (the original F-Stack path).
2423

25-
This feature = **solidify that coexistence capability as the primary baseline (hook mode)** + **add unified-event coexistence to the native `ff_api` mode**, gated by the `FF_KERNEL_COEXIST` compile macro, rather than building a side path that bypasses F-Stack.
24+
> **Implementation-status note (anti-speculation)**: v6's native map `ff_native_fd_map` and the default dual-build/dual-drive logic are **NOT yet landed in `lib/`** (grep confirms `lib/` has no `ff_native_fd_map`). This directory's v6 docs are the **upgraded design**; they explicitly distinguish "v5 already measured/landed" from "v6 to-be-implemented design" and must not present any v6 behavior as a fait accompli.
2625
27-
> **v3 history**: v3 routed `ff_socket(SOCK_KERNEL)` to `ff_host_socket()` → a raw host `socket()`, completely bypassing the F-Stack user-space stack (a fundamental mistake), reverted in v4 and rewritten to the paradigm above.
26+
> **v3 historical mistake (reverted)**: v3 routed `ff_socket(SOCK_KERNEL)` to `ff_host_socket()` → raw host `socket()`, completely bypassing F-Stack — a fundamental mistake, reverted (commit 0748eff94). v6's "dual-stack" is never a bypass: F-Stack is always built and always present; the kernel stack is a **parallel additional** second stack.
27+
28+
---
2829

2930
## 1. One-Sentence Goal
3031

31-
Let an F-Stack application, **while running its business fast path on the F-Stack user-space stack**, route some sockets/listens/connects to the host kernel stack on a per-fd basis (so local `ping`/`curl`/`ssh` can directly reach its kernel-stack services, and the app as a client can `connect` via the kernel stack to local/external kernel services), with both stacks' fds sent/received in the **same epoll/event loop****F-Stack is always present and is never replaced by a side path**.
32+
Let an F-Stack application be **dual-stack by default**: the same socket/listen works on BOTH the F-Stack user-space stack (DPDK+FreeBSD, business fast path) and the host Linux kernel stack local `ping`/`curl`/`ssh` reach the kernel-stack side, remote peers reach the F-Stack side via the DPDK NIC, and both stacks' events are sent/received in the **same epoll/event loop****F-Stack is always present, never replaced by a side path**; use `SOCK_KERNEL`/`SOCK_FSTACK` markers to override to single-stack when needed.
3233

3334
## 2. Scope Statement (Important)
3435

35-
- **This feature = dual-stack coexistence**: F-Stack user-space stack (business, default) + host kernel stack (per-fd `SOCK_KERNEL`), in the same process and event loop.
36-
- **Selection method**: per-fd `SOCK_KERNEL`/`SOCK_FSTACK` markers (default F-Stack); one **coexistence-capability switch** in config.ini (whether to enable kernel-stack coexistence, without changing the default per-fd F-Stack semantics).
37-
- **Hook mode (primary baseline, already supported)**: directly reuse `FF_KERNEL_EVENT``ff_hook_socket` marker-based selection + `fstack_kernel_fd_map` dual-stack epoll merge; this round solidifies it and provides a correct coexistence demo.
38-
- **Native `ff_api` mode (new design)**: `ff_epoll_*` is currently a pure kqueue wrapper with no kernel-fd awareness; this round adds, inside lib, an fd-ownership table + a kernel-epoll mirror + an `ff_epoll_wait` merge of kqueue⊕epoll, so natively-linked applications can also coexist over both stacks in one process.
36+
- **This feature = automatic dual-stack coexistence**: with no marker, F-Stack + kernel dual-stack, dual-drive, unified events; markers override to single-stack.
37+
- **Compile-macro gating (outermost)**: all coexistence code is gated by `FF_KERNEL_COEXIST`, **commented off by default** in `lib/Makefile`; when off, coexistence code is not compiled and is byte-for-byte zero regression vs. upstream.
38+
- **Runtime switch**: `config.ini [stack] kernel_coexist` (effective only when the macro is on), `=1` enables automatic dual-stack, `=0` degrades to pure F-Stack (zero regression).
39+
- **Selection markers**: `SOCK_KERNEL` (kernel only) / `SOCK_FSTACK` (F-Stack only) override the default dual-stack.
40+
- **Hook mode (cross-reference baseline)**: `adapter/syscall`'s `FF_KERNEL_EVENT` already supports "app ON F-Stack + epoll dual-build merge + close linkage", **but socket/listen are NOT dual-built** (kernel listen requires explicit `SOCK_KERNEL`). The native v6 divergence = socket/bind/listen/connect **are also auto dual-built / dual-driven**.
3941
- **Explicitly excluded**:
40-
- Do **not** create a side socket that bypasses F-Stack (the v3 `ff_host_socket` approach is deprecated).
41-
- Do **not** add an anti-F-Stack "whole-process default-to-kernel" global switch (the v3 `default_stack=kernel` is deprecated).
42-
- Do **not** create a new `ff_local_*` dual API / mTCP-like dual namespace.
43-
- Do **not** do gazelle-style thread-level selection (the F-Stack multi-process model relies on different config files).
42+
- Do **not** create a side socket that bypasses F-Stack (v3 `ff_host_socket` raw bypass deprecated).
43+
- Do **not** add an anti-F-Stack "whole-process default-to-kernel" switch (v3 `default_stack=kernel` deprecated).
44+
- Do **not** create an `ff_local_*` dual API / mTCP-like dual namespace.
45+
- Do **not** do gazelle-style thread-level selection (F-Stack multi-process relies on different config files).
4446
- Do **not** adopt KNI/`rte_kni`/virtio-user packet reinjection (boundary clarification only).
4547

4648
## 3. Reading Path
4749

4850
| Order | Document | Purpose |
4951
|---|---|---|
50-
| 1 | `plan.md` | Plan, team, gate, rework paradigm |
51-
| 2 | `01-requirements-spec.md` | Requirements and goals/non-goals (coexistence) |
52-
| 3 | `02-current-state-analysis.md` | Current state of hook FF_KERNEL_EVENT / nginx kernel_network_stack / native event layer (code is authoritative) |
53-
| 4 | `03-external-research.md` | External solution research (with URLs) |
54-
| 5 | `04-architecture-design.md` | Coexistence architecture, dual-stack unified events, bidirectional data flow |
55-
| 6 | `05-interface-design.md` | Marker/config contracts, hook and native dual-mode adaptation |
56-
| 7 | `06-milestones.md` | Milestones and coding work list |
57-
| 8 | `07-test-spec.md` | Test and performance-baseline plan |
58-
| 9 | `08-review-gate.md` | Review gate conclusion |
52+
| 1 | `01-requirements-spec.md` | Requirements and goals/non-goals (auto dual-stack + marker single-stack override) |
53+
| 2 | `02-current-state-analysis.md` | v5 measured state + native vs hook isomorphism/divergence table + v6 map gap |
54+
| 3 | `03-external-research.md` | External research (with URLs, low-trust corroboration) |
55+
| 4 | `04-architecture-design.md` | Dual-drive data flow + fd tri-state routing + dual-stack events + map |
56+
| 5 | `05-interface-design.md` | socket/bind/listen/connect/accept/close/epoll_* dual-stack contracts + marker semantics + connect draft + exception matrix |
57+
| 6 | `06-milestones.md` | R6→R7 automatic dual-stack milestones |
58+
| 7 | `07-test-spec.md` | cmocka dual-mode cases + real-machine dual-stack plan |
59+
| 8 | `08-review-gate.md` | Gate conclusion (v6 R7 "pending measurement") |
60+
| 9 | `09-impl-plan.md` | Per-file rework steps |
61+
| 10 | `10-perf-baseline-report.md` | No-regression criteria of dual-stack on the F-Stack fast path |
5962

6063
## 4. Glossary
6164

6265
| Term | Meaning |
6366
|---|---|
64-
| F-Stack stack | DPDK PMD + user-space FreeBSD protocol stack (business fast path, **default stack**) |
65-
| Kernel stack | Host Linux kernel network protocol stack (local/management/client connecting to local or external kernel services) |
66-
| Coexistence | F-Stack fds and kernel fds present **in the same process and the same event loop**, each using its own stack |
67-
| Stack-selection marker | `SOCK_KERNEL`(0x02000000)/`SOCK_FSTACK`(0x01000000) on socket `type`, per-fd |
68-
| Coexistence-capability switch | A config.ini switch for whether to enable kernel-stack coexistence (does not change the default per-fd F-Stack semantics) |
69-
| fd ownership | The marker at creation fixes whether the fd belongs to F-Stack or the kernel; subsequent syscalls/events route by ownership (`is_fstack_fd`/`CHECK_FD_OWNERSHIP`) |
70-
| Unified events | External epoll style, internally merging F-Stack kqueue events + kernel epoll events |
71-
| Hook mode | LD_PRELOAD takes over the POSIX API (`ff_hook_*`) + `FF_KERNEL_EVENT`; coexistence already supported |
72-
| Native mode | The app directly calls `ff_*` (`ff_api.h`) + the `ff_run` main loop; this round adds unified-event coexistence |
67+
| F-Stack stack | DPDK PMD + user-space FreeBSD protocol stack (business fast path, **always present**) |
68+
| Kernel stack | Host Linux kernel network protocol stack (local/management/client to local or external kernel services) |
69+
| **Automatic dual-stack (v6)** | With no marker, one `ff_socket` builds both an F-Stack fd and a kernel host fd; each `ff_*` dual-drives both stacks |
70+
| **Dual-stack fd (v6)** | The F-Stack raw fd returned to the app, mapped to one kernel host fd in `ff_native_fd_map` |
71+
| **native map (v6, to-be-implemented)** | `ff_native_fd_map[fstack_fd]=host_fd` (`ff_host_interface.c`, 65536 entries), modeled on adapter `fstack_kernel_fd_map` |
72+
| Managed kernel fd (kernel-only) | `ff_kernel_fd_encode(host_fd)` (≥`FF_KERNEL_FD_BASE`=0x40000000), returned by `SOCK_KERNEL` or a kernel-side accepted connection; no map entry |
73+
| `FF_KERNEL_COEXIST` | **Compile macro** gating whether all coexistence code is compiled; off by default (commented) in `lib/Makefile` (compile-time switch) |
74+
| Selection marker | `SOCK_KERNEL`(0x02000000)/`SOCK_FSTACK`(0x01000000) on socket `type`, single-stack override of the default dual-stack (wrapped by `FF_KERNEL_COEXIST`, opt-in) |
75+
| Coexistence-capability switch | `config.ini [stack] kernel_coexist`, **runtime** enabling of automatic dual-stack (effective only when the macro is on) |
76+
| fd tri-state routing | `ff_is_kernel_fd(fd)`(≥BASE)=kernel only; else the F-Stack path, and if `ff_native_map_get(fd)>0` also dual-drive host_fd |
77+
| Unified events | External epoll style; internally merges F-Stack kqueue events + kernel epoll events (a dual-stack listen is registered once on each stack) |
78+
| Hook mode | LD_PRELOAD takes over the POSIX API (`ff_hook_*`) + `FF_KERNEL_EVENT`; epoll dual-build merge, socket/listen NOT dual-built |
79+
| Native mode | The app directly calls `ff_*` (`ff_api.h`) + the `ff_run` main loop; v6 implements automatic dual-stack |
7380

7481
## 5. Sources
7582

76-
- F-Stack actual code (`adapter/syscall/`, `app/nginx-1.28.0/`, `lib/`) — **highest priority; on conflict, code is authoritative**.
83+
- F-Stack actual code (`lib/`, `adapter/syscall/`, `app/nginx-1.28.0/`) — **highest priority; on conflict, code is authoritative**.
7784
- F-Stack three-tier architecture docs and knowledge graph (`docs/`), `adapter/syscall/README.md`.
78-
- Public external materials (GitHub/technical blogs, etc.), all with accessible URLs in `03`.
85+
- Public external materials (GitHub/technical blogs, etc.), all with accessible URLs in `03`, **low-trust corroboration only**.

docs/kernel_event_support_spec/01-requirements-spec.md

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
1-
# 01 Requirements Spec: F-Stack User-Space Stack + Local Kernel Stack COEXISTENCE
1+
# 01 Requirements Spec: F-Stack User-Space Stack + Local Kernel Stack AUTOMATIC DUAL-STACK COEXISTENCE
22

33
> **Document ID**: SPEC-KE-01
4-
> **Version**: v5 (compile-macro gating: `FF_KERNEL_COEXIST` off by default + runtime `kernel_coexist` dual-layer switch)
4+
> **Version**: v6 (native automatic dual-stack coexistence paradigm)
55
> **Date**: 2026-06-17
6-
> **Status**: Drafting
6+
> **Status**: Drafting (v6 design)
77
> **Scope**: Define the problem domain, goals/non-goals, functional and non-functional requirements, and success criteria of this feature.
8-
9-
> **v5 sync (key points; see `zh_cn/01-requirements-spec.md` for full detail)**:
10-
> - **FR-10 (compile-macro gating, opt-in)**: all `lib/` coexistence code is wrapped by `FF_KERNEL_COEXIST`, commented off by default in `lib/Makefile:57-60` (`#FF_KERNEL_COEXIST=1`); macro block at `lib/Makefile:174-177` adds `-DFF_KERNEL_COEXIST` to both `HOST_CFLAGS` and `CFLAGS`. Macro off → `libfstack.a` byte-for-byte zero regression (`nm`/`objdump` shows no `ff_host_*`/`ff_epoll_pairs` symbols).
11-
> - **FR-11 (marker opt-in visibility)**: `SOCK_FSTACK`/`SOCK_KERNEL` (`ff_api.h:81-99`) are wrapped by the macro; an APP must also define `FF_KERNEL_COEXIST` to see them.
12-
> - **NFR-1 strengthened to a compile-time guarantee**: when the macro is undefined, coexistence code is not compiled at all.
13-
> - **G4** clarified: config `kernel_coexist` is a **runtime** switch effective only when the compile macro is on.
8+
> **Authoritative full text**: `zh_cn/01-requirements-spec.md` (Chinese is the maintained source of truth; this English file carries the v6 key points). On conflict, code is authoritative.
9+
10+
> **v6 sync (key points; see `zh_cn/01-requirements-spec.md` for full detail)**:
11+
> - **Paradigm upgrade**: from v5 per-fd either/or to v6 **automatic dual-stack**. When `FF_KERNEL_COEXIST` is compiled in AND `kernel_coexist=1`, a default (no-marker) `ff_socket` builds BOTH an F-Stack fd and a kernel host fd, registers `ff_native_fd_map[fstack_fd]=host_fd`, returns the F-Stack raw fd; `ff_bind/ff_listen/ff_close/ff_connect` dual-drive both stacks. One `listen(80)` listens on both F-Stack (DPDK) and the Linux kernel stack.
12+
> - **FR-2/FR-3/FR-4 (auto dual-stack, v6 to-be-implemented)**: `ff_socket` dual-build; `ff_bind/ff_listen` dual-drive; one-listen-many-uses (remote `9.134.214.176:80` via DPDK NIC + local `curl 127.0.0.1:80` via kernel).
13+
> - **FR-6 (accept single-stack ownership, Q3=A)**: a dual-stack listen fd's accept returns a single-stack connection fd (F-Stack raw / kernel encode); subsequent recv/send/close route by `ff_is_kernel_fd`, NOT consulting the map (hot path, NFR-2).
14+
> - **FR-8/FR-9 (marker single-stack override)**: `SOCK_KERNEL` = kernel only (encode, no map); `SOCK_FSTACK` = F-Stack only (no map, zero regression). `SOCK_FSTACK` wins.
15+
> - **FR-10 / N7 (connect dual-stack, DRAFT, PENDING USER CONFIRMATION)**: a single client logical flow cannot truly duplex over both stacks; v6 contract = "F-Stack primary + kernel concurrent connect for dual-network reachability". Use `SOCK_KERNEL` for a pure-kernel client; kernel-primary/failover is future work. See `zh_cn/05 §6`.
16+
> - **FR-12 (compile-macro gating, opt-in)**: all `lib/` coexistence code (incl. the v6 `ff_native_fd_map`) is wrapped by `FF_KERNEL_COEXIST`, off by default (`lib/Makefile:57-60`/`:174-177`); macro off → byte-for-byte zero regression (`nm`/`objdump` shows no `ff_host_*`/`ff_epoll_pairs`/`ff_native_fd_map`).
17+
> - **NFR-1/NFR-2/NFR-3**: triple zero-regression guarantee (macro off / `kernel_coexist=0` / `SOCK_FSTACK`); hot path no regression (single-stack connection recv/send do not consult the map); F-Stack always present.
18+
> - **Implementation status (D9, anti-speculation)**: `ff_native_fd_map` and default dual-build/dual-drive are NOT yet landed (`zh_cn/02 §5.2` grep=0). v6 FR/NFR new items are to-be-implemented; acceptance pending R7.
1419
1520
---
1621

0 commit comments

Comments
 (0)