Skip to content

Commit 03f244a

Browse files
committed
fix(stack-coexist): support ff_kqueue/ff_kevent coexistence and IPv6 dual-stack bind
ff_kqueue/ff_kevent now pair a host epoll per kqueue (like ff_epoll) so kernel-fd events reach kqueue-based apps (example/main.c); fixes kernel-side 127.0.0.1 returning no data. Set IPV6_V6ONLY on the dual-stack host IPv6 socket so it coexists with the host IPv4 socket on the same port; fixes INET6 helloworld failing to start (EADDRINUSE). All under FF_KERNEL_COEXIST, macro-off byte-identical. Adds cmocka tests and syncs zh/en spec + three-layer docs.
1 parent b3db46f commit 03f244a

37 files changed

Lines changed: 741 additions & 102 deletions

docs/01-LAYER1-ARCHITECTURE.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ NIC Hardware
5555
│ ├── ff_dpdk_if.c (2907 lines) # DPDK NIC interface layer - most critical
5656
│ ├── ff_glue.c (1467 lines) # FreeBSD glue layer
5757
│ ├── ff_config.c (1694 lines) # Configuration parsing
58-
│ ├── ff_syscall_wrapper.c (2125 lines) # Linux→FreeBSD system call adaptation
59-
│ ├── ff_host_interface.c # Host interface (pthread/mmap/time)
58+
│ ├── ff_syscall_wrapper.c (2212 lines) # Linux→FreeBSD system call adaptation (+ R9 kqueue coexist + IPV6_V6ONLY)
59+
│ ├── ff_host_interface.c (583 lines) # Host interface (pthread/mmap/time) + FF_KERNEL_COEXIST bridges (27 ff_host_*)
6060
│ ├── ff_init.c (70 lines) # Initialization coordination
61-
│ ├── ff_epoll.c (~134 lines) # epoll → kqueue conversion
61+
│ ├── ff_epoll.c (289 lines) # epoll → kqueue conversion (unified F-Stack + kernel)
6262
│ ├── ff_dpdk_kni.c # Virtual NIC support (via virtio_user, no longer depends on rte_kni.ko)
6363
│ ├── ff_route.c (1604 lines) # Route socket / RIB hooks (rtsock partial port)
6464
│ ├── ff_veth.c (1132 lines) # Virtual ethernet device (M4: full if_t accessor rewrite)
@@ -119,10 +119,10 @@ NIC Hardware
119119
| **ff_dpdk_if.c** | 2907 | NIC driver/DPDK operations/core TX/RX logic | DPDK, ff_glue |
120120
| **ff_glue.c** | 1467 | FreeBSD kernel emulation/memory/locks/interrupts (8-category 14.0+ ABI fixes at M4) | FreeBSD headers, DPDK |
121121
| **ff_config.c** | 1694 | INI configuration file parsing | ff_ini_parser |
122-
| **ff_syscall_wrapper.c** | 2125 | Linux system call → FreeBSD adaptation (sockaddr update at M4; FF_KERNEL_COEXIST routing) | FreeBSD sys |
122+
| **ff_syscall_wrapper.c** | 2212 | Linux system call → FreeBSD adaptation (sockaddr update at M4; FF_KERNEL_COEXIST routing; R9 kqueue coexist + IPV6_V6ONLY) | FreeBSD sys |
123123
| **ff_init.c** | 70 | Initialization flow coordination | All above modules |
124-
| **ff_epoll.c** | ~134 | Linux epoll → FreeBSD kqueue conversion | FreeBSD kqueue |
125-
| **ff_host_interface.c** | ~285 | Host OS interface (mmap/pthread/rand) | System libraries |
124+
| **ff_epoll.c** | 289 | Linux epoll → FreeBSD kqueue conversion (unified F-Stack + kernel; ff_epoll_host_ep shared with kqueue path) | FreeBSD kqueue |
125+
| **ff_host_interface.c** | 583 | Host OS interface (mmap/pthread/rand) + FF_KERNEL_COEXIST host-stack bridges (27 ff_host_*) | System libraries |
126126
| **ff_dpdk_kni.c** | ~441 | Virtual NIC support (via virtio_user, no longer depends on rte_kni.ko) | DPDK virtio_user |
127127
| **ff_route.c** | 1604 | Route socket / RIB hooks (rtsock partial port; 5-category 14.0+ ABI fixes at M4) | FreeBSD net/route |
128128
| **ff_veth.c** | 1132 | Virtual ethernet device (28 if_t accessor rewrites at M4) | FreeBSD net/if |
@@ -310,8 +310,9 @@ An optional mode (compile-time macro `FF_KERNEL_COEXIST` + runtime `config.ini [
310310

311311
- `ff_socket()` selects the stack via `SOCK_FSTACK` / `SOCK_KERNEL` flags; with no flag it **dual-creates** an F-Stack socket plus a paired host socket.
312312
- A kernel-stack fd is returned as `host_fd + 0x40000000` (`FF_KERNEL_FD_BASE`), which never collides with FreeBSD fds (`< 65536`); entries route such fds to thin `ff_host_*` host-libc bridges.
313-
- The F-Stack ↔ host fd pairing is held in `ff_native_fd_map`; `ff_epoll_*` lazily pairs a host `epoll` per kqueue for unified event delivery.
314-
- When the macro is off the library is byte-for-byte identical to the pure-F-Stack build. Known limitation: `ff_readv`/`ff_writev`/`ff_ioctl` are not yet kernel-routed. See `docs/kernel_event_support_spec/`.
313+
- The F-Stack ↔ host fd pairing is held in `ff_native_fd_map`; `ff_epoll_*` lazily pairs a host `epoll` per kqueue for unified event delivery. A dual-built `AF_INET6` socket gets `IPV6_V6ONLY=1` on its host counterpart (`ff_host_set_v6only`, R9) so v4+v6 coexist on the same port (fixes the prior `-DINET6` `errno=98` startup failure).
314+
- **R9** extends unified events to the native `ff_kqueue`/`ff_kevent` interface (shared `ff_epoll_host_ep`): `ff_kevent` registers a kernel/dual-stack fd's `EVFILT_READ/WRITE` into the kqueue-paired host epoll and synthesizes `struct kevent` (`ident`=app-side fd) from a non-blocking host-epoll poll before merging F-Stack events — a pure-kqueue app (`example/main.c`) now reaches the kernel-side listener (`curl 127.0.0.1:80`=200, was 000).
315+
- When the macro is off the library is byte-for-byte identical to the pure-F-Stack build. Known limitations: `ff_readv`/`ff_writev`/`ff_ioctl` are not yet kernel-routed; kernel fds via kqueue support `EVFILT_READ/WRITE` only. See `docs/kernel_event_support_spec/`.
315316

316317
## 7. Technology Selection Analysis
317318

docs/02-LAYER2-INTERFACES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ int ff = ff_socket(AF_INET, SOCK_STREAM | SOCK_FSTACK, 0); // F-Stack stack
186186
int du = ff_socket(AF_INET, SOCK_STREAM, 0); // dual-created (default)
187187
```
188188

189-
A kernel-stack fd is returned as `host_fd + 0x40000000` and is accepted transparently by all `ff_*` calls (forwarded to `ff_host_*` bridges), including `ff_epoll_*`. Off by default → byte-for-byte identical build. Known limitation: `ff_readv` / `ff_writev` / `ff_ioctl` are not yet kernel-routed. See `docs/kernel_event_support_spec/`.
189+
A kernel-stack fd is returned as `host_fd + 0x40000000` and is accepted transparently by all `ff_*` calls (forwarded to `ff_host_*` bridges), including `ff_epoll_*` and — since **R9**`ff_kqueue`/`ff_kevent`: each kqueue lazily pairs one host epoll (shared `ff_epoll_host_ep`), `ff_kevent` registers a kernel/dual-stack fd's `EVFILT_READ/WRITE` there and synthesizes `struct kevent` (`ident`=app-side fd) from a non-blocking host-epoll poll before merging F-Stack events — so a pure-kqueue app (`example/main.c`) reaches the kernel-side listener (`curl 127.0.0.1:80`=200). A dual-built `AF_INET6` socket gets `IPV6_V6ONLY=1` on its host counterpart so a `-DINET6` build starts with v4+v6 on the same port. Off by default → byte-for-byte identical build. Known limitations: `ff_readv` / `ff_writev` / `ff_ioctl` are not yet kernel-routed; kernel fds via kqueue support `EVFILT_READ/WRITE` only. See `docs/kernel_event_support_spec/`.
190190

191191
## 3. Application Development Guidelines
192192

docs/03-LAYER3-FUNCTIONS.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ struct ff_dispatcher_context {
308308

309309
## 3. Three Key Source File Analyses
310310

311-
### 3.1 ff_syscall_wrapper.c (2125 Lines) - Linux/FreeBSD Adaptation
311+
### 3.1 ff_syscall_wrapper.c (2212 Lines) - Linux/FreeBSD Adaptation
312312

313313
**Main Responsibility**: Convert Linux system call parameters/options to FreeBSD equivalents
314314

@@ -564,8 +564,10 @@ struct prison prison0; // Namespace
564564
### 3.4 Kernel-Stack Coexistence Files (`FF_KERNEL_COEXIST`, optional)
565565

566566
Compiled only with `FF_KERNEL_COEXIST=1`:
567-
- **`ff_host_interface.c` (528 L) / `.h` (178 L)**: `FF_KERNEL_FD_BASE=0x40000000` + inline `ff_is_kernel_fd/ff_kernel_fd_encode/ff_kernel_fd_real`; `ff_native_fd_map[65536]` + `ff_native_map_get/set/clear`; **24 `ff_host_*` host-libc bridges**.
568-
- **`ff_epoll.c` (277 L)**: `ff_epoll_pairs[64]` lazily pairs a host `epoll` per kqueue; `ff_epoll_wait` polls host epoll (non-blocking) then merges kqueue events (single-threaded, no lock).
567+
- **`ff_host_interface.c` (583 L) / `.h` (182 L)**: `FF_KERNEL_FD_BASE=0x40000000` + inline `ff_is_kernel_fd/ff_kernel_fd_encode/ff_kernel_fd_real`; `ff_native_fd_map[65536]` + `ff_native_map_get/set/clear`; **27 `ff_host_*` host-libc bridges** (R9 added `ff_host_set_v6only`, `ff_host_kqueue_ctl`, `ff_host_kqueue_poll`).
568+
- **`ff_epoll.c` (289 L)**: `ff_epoll_pairs[64]` lazily pairs a host `epoll` per kqueue; `ff_epoll_wait` polls host epoll (non-blocking) then merges kqueue events (single-threaded, no lock). `ff_epoll_host_ep` is shared (promoted from `static`) so the R9 kqueue path reuses the same pairing table.
569+
- **R9 kqueue/kevent coexistence (`ff_syscall_wrapper.c`)**: `ff_kqueue`/`ff_kevent` mirror the epoll path — register a kernel/dual-stack fd's `EVFILT_READ/WRITE` into the kqueue-paired host epoll (`ff_host_kqueue_ctl`), synthesize `struct kevent` from a non-blocking `ff_host_kqueue_poll` (`ident`=app-side fd, `EV_EOF``EPOLLHUP|ERR`), then merge `ff_kevent_do_each` F-Stack events. Fixes the `example/main.c` kqueue model (kernel-side `curl 127.0.0.1:80`=200, was 000). Kernel fds: `EVFILT_READ/WRITE` only.
570+
- **R9 IPv6**: a dual-built `AF_INET6` socket gets `IPV6_V6ONLY=1` on its host counterpart (`ff_host_set_v6only`), so a `-DINET6` build starts with v4+v6 on the same port (fixes the prior `errno=98 EADDRINUSE`).
569571
- **`ff_syscall_wrapper.c`**: `ff_socket` dual-create + per-entry kernel-fd routing. Not routed: `ff_readv`/`ff_writev`/`ff_ioctl`.
570572

571573
## 4. Key Header File Overview
@@ -576,7 +578,7 @@ Compiled only with `FF_KERNEL_COEXIST=1`:
576578
| `ff_config.h` | ~100 | Configuration structure definitions |
577579
| `ff_event.h` | ~150 | kevent structures and macros |
578580
| `ff_errno.h` | ~100 | 96 errno mappings |
579-
| `ff_host_interface.h` | 178 | OS abstraction layer (pthread/mmap) + `FF_KERNEL_COEXIST` kernel-fd helpers & 24 `ff_host_*` bridge decls |
581+
| `ff_host_interface.h` | 182 | OS abstraction layer (pthread/mmap) + `FF_KERNEL_COEXIST` kernel-fd helpers & 27 `ff_host_*` bridge decls (incl. R9 set_v6only/kqueue_ctl/kqueue_poll) |
580582
| `ff_dpdk_if.h` | ~50 | DPDK initialization interface |
581583
| `ff_veth.h` | ~100 | Virtual Ethernet and mbuf operations |
582584
| `ff_log.h` | ~50 | Log levels and macros |

docs/F-Stack_Architecture_Layer1_System_Overview.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,10 @@ Actual data on 10GbE link:
8484
│ ├── ff_dpdk_if.c (2907 lines) # ⭐ Most critical: DPDK/NIC driver
8585
│ ├── ff_glue.c (1467 lines) # Kernel emulation layer
8686
│ ├── ff_config.c (1694 lines) # Configuration parsing
87-
│ ├── ff_syscall_wrapper.c (2125 lines) # Linux↔FreeBSD adaptation
87+
│ ├── ff_syscall_wrapper.c (2212 lines) # Linux↔FreeBSD adaptation (+ R9 kqueue coexist + IPV6_V6ONLY)
8888
│ ├── ff_init.c (69 lines) # Initialization coordination
89-
│ ├── ff_epoll.c (277 lines) # Epoll compat (unified F-Stack+kernel)
90-
│ ├── ff_host_interface.c (528 lines) # Host OS iface + FF_KERNEL_COEXIST bridges
89+
│ ├── ff_epoll.c (289 lines) # Epoll compat (unified F-Stack+kernel)
90+
│ ├── ff_host_interface.c (583 lines) # Host OS iface + FF_KERNEL_COEXIST bridges (27 ff_host_*)
9191
│ ├── ff_dpdk_kni.c # Virtual NIC support
9292
│ ├── ff_*.h # API and data structure definitions
9393
│ └── Makefile (765 lines) # Build system
@@ -157,8 +157,8 @@ Actual data on 10GbE link:
157157
| **NIC Driver Layer** | ff_dpdk_if.c | 2907 | DPDK initialization, NIC operations, core TX/RX logic | DPDK, ff_glue |
158158
| **Glue Layer** | ff_glue.c | 1467 | Kernel API emulation (locks, memory, interrupts; M4 8-category 14.0+ ABI fixes) | FreeBSD sys, pthread |
159159
| **Configuration System** | ff_config.c | 1694 | INI file parsing, runtime parameter management | ff_ini_parser |
160-
| **Linux Compatibility** | ff_syscall_wrapper.c | 2125 | Socket option/errno mapping (M4 sockaddr update; FF_KERNEL_COEXIST routing) | FreeBSD API |
161-
| **Epoll Compatibility** | ff_epoll.c | 277 | Linux epoll → FreeBSD kqueue (unified F-Stack + kernel epoll) | ff_kqueue |
160+
| **Linux Compatibility** | ff_syscall_wrapper.c | 2212 | Socket option/errno mapping (M4 sockaddr update; FF_KERNEL_COEXIST routing; R9 kqueue coexist + IPV6_V6ONLY) | FreeBSD API |
161+
| **Epoll Compatibility** | ff_epoll.c | 289 | Linux epoll → FreeBSD kqueue (unified F-Stack + kernel epoll; ff_epoll_host_ep shared with kqueue path) | ff_kqueue |
162162
| **Initialization Coordination** | ff_init.c | 69 | Startup flow orchestration | All other modules |
163163
| **Host Interface** | ff_host_interface.c | - | mmap/pthread/time interfaces | System libraries |
164164
| **Virtual NIC** | ff_dpdk_kni.c | - | Kernel virtual NIC support | DPDK KNI |
@@ -434,10 +434,10 @@ By default every socket lives purely in the F-Stack user-space stack. The option
434434
**How it works:**
435435
- **Per-socket stack selection.** `ff_socket()` honours two flags OR-ed into `type`: `SOCK_FSTACK` forces the F-Stack stack, `SOCK_KERNEL` forces the host kernel stack. With no flag (and coexistence enabled) the socket is **dual-created** — an F-Stack socket plus a paired host-kernel socket. Priority: per-socket marker > config `kernel_coexist` > F-Stack.
436436
- **Managed kernel-fd space.** A kernel-stack fd is handed to the application as `host_fd + FF_KERNEL_FD_BASE` (`0x40000000`), far above the maximum FreeBSD fd (`kern.maxfiles <= 65536`), so the two fd ranges never collide. F-Stack entry points recognise such an fd and route it to a thin host-libc bridge; the default F-Stack path is left untouched.
437-
- **Dual-stack fd pairing.** For a dual-created socket the F-Stack fd ↔ host fd pairing is tracked in `ff_native_fd_map`, so control/data operations drive both stacks where it matters (e.g. `bind`/`listen` on both, `shutdown`/`close` on both).
438-
- **Unified event loop.** `ff_epoll_*` lazily pairs one host `epoll` fd per kqueue, so kernel-fd and F-Stack events are delivered from the single `ff_epoll_wait()` the application already runs.
437+
- **Dual-stack fd pairing.** For a dual-created socket the F-Stack fd ↔ host fd pairing is tracked in `ff_native_fd_map`, so control/data operations drive both stacks where it matters (e.g. `bind`/`listen` on both, `shutdown`/`close` on both). For a dual-created `AF_INET6` socket the host counterpart is set to `IPV6_V6ONLY=1` (`ff_host_set_v6only`, R9) so it coexists with the same-port host IPv4 socket; this fixes the prior `-DINET6` startup failure (`ff_bind` `errno=98 EADDRINUSE`).
438+
- **Unified event loop.** `ff_epoll_*` lazily pairs one host `epoll` fd per kqueue, so kernel-fd and F-Stack events are delivered from the single `ff_epoll_wait()` the application already runs. **R9** extends the same mechanism to the native `ff_kqueue`/`ff_kevent` interface (shared `ff_epoll_host_ep` pairing): `ff_kevent` registers a kernel/dual-stack fd's `EVFILT_READ/WRITE` into the kqueue-paired host epoll and synthesizes `struct kevent` (`ident`=the app-side fd) from a non-blocking host-epoll poll before merging F-Stack kqueue events — so a pure-kqueue app (`example/main.c`) now reaches the kernel-side listener (measured `curl 127.0.0.1:80` = 200 size=438, was 000).
439439
440-
**Known limitations (this release):** `ff_readv` / `ff_writev` / `ff_ioctl` are not yet routed to the kernel stack — use `ff_read` / `ff_write` for dual-stack fds. Full design, test, and review-gate record: `docs/kernel_event_support_spec/` (+ `zh_cn/`).
440+
**Known limitations (this release):** `ff_readv` / `ff_writev` / `ff_ioctl` are not yet routed to the kernel stack — use `ff_read` / `ff_write` for dual-stack fds; kernel fds via kqueue support `EVFILT_READ/WRITE` only. Full design, test, and review-gate record: `docs/kernel_event_support_spec/` (+ `zh_cn/`).
441441
442442
---
443443

docs/F-Stack_Architecture_Layer2_Interface_Specification.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,8 @@ Priority: per-socket marker > `config.ini [stack] kernel_coexist` > F-Stack. Whe
637637

638638
**Transparent routing.** All standard `ff_*` calls accept a managed kernel fd transparently: `ff_bind/listen/connect/accept[4]/close/read/write/recv*/send*/sendmsg/recvmsg/getpeername/getsockname/shutdown/setsockopt/getsockopt/fcntl`, and `ff_epoll_ctl/wait`. Internally each kernel fd is forwarded to a thin `ff_host_*` host-libc bridge (`lib/ff_host_interface.c`). **Not yet routed (known limitation):** `ff_readv` / `ff_writev` / `ff_ioctl` — use `ff_read` / `ff_write` for kernel/dual-stack fds. Full design and tests: `docs/kernel_event_support_spec/`.
639639

640+
**R9 — kqueue/kevent coexistence + IPv6.** The unified-event support now also covers the native `ff_kqueue` / `ff_kevent` interface (previously only `ff_epoll_*`): each kqueue lazily pairs one host epoll (shared `ff_epoll_host_ep`, reusing the `ff_epoll_pairs` table). `ff_kevent` registers a kernel/dual-stack fd's `EVFILT_READ/WRITE` into that host epoll (kernel-only changes are not forwarded to the F-Stack kqueue), and on wait it synthesizes `struct kevent` (`ident`=the app-side fd, `EV_EOF``EPOLLHUP|ERR`) from a non-blocking host-epoll poll before merging F-Stack kqueue events. This makes a pure-kqueue application (e.g. `example/main.c`) reach the kernel-side listener — measured `curl 127.0.0.1:80` = 200 size=438. Kernel fds via kqueue support `EVFILT_READ/WRITE` only. On the IPv6 side, a dual-built `AF_INET6` socket has its host counterpart set to `IPV6_V6ONLY=1` (`ff_host_set_v6only`) so a `-DINET6` build starts cleanly with v4+v6 on the same port (fixes the prior host-IPv6 `errno=98 EADDRINUSE`).
641+
640642
---
641643

642644
## 4. Multi-Process and Multi-Thread Interfaces

0 commit comments

Comments
 (0)