feat(net): Fast Path/Zero-Copy Bypass & Network Stack Documentation
Implemented Fast Path filter for UDP/9999 UTCP tunnel traffic, bypassing LwIP stack. Added zero-copy header stripping in fastpath.nim. Documented full network stack architecture in docs/NETWORK_STACK.md. Verified ICMP ping and LwIP graft functionality.
This commit is contained in:
parent
de971b465e
commit
08d31f879c
|
|
@ -0,0 +1,98 @@
|
||||||
|
# SPDX-License-Identifier: LSL-1.0
|
||||||
|
# Copyright (c) 2026 Markus Maiwald
|
||||||
|
# Stewardship: Self Sovereign Society Foundation
|
||||||
|
#
|
||||||
|
# This file is part of the Nexus Sovereign Core.
|
||||||
|
# See legal/LICENSE_SOVEREIGN.md for license terms.
|
||||||
|
|
||||||
|
## Fast Path Bypass (SPEC-700)
|
||||||
|
##
|
||||||
|
## Intercepts UTCP tunnel traffic (UDP/9999) before LwIP processing.
|
||||||
|
## Zero-copy header stripping via pointer arithmetic.
|
||||||
|
|
||||||
|
# Constants
|
||||||
|
const
|
||||||
|
UTCP_TUNNEL_PORT* = 9999'u16
|
||||||
|
ETHERTYPE_IPV4* = 0x0800'u16
|
||||||
|
IPPROTO_UDP* = 17'u8
|
||||||
|
|
||||||
|
# Header sizes
|
||||||
|
ETH_HEADER_LEN* = 14
|
||||||
|
IP_HEADER_LEN* = 20
|
||||||
|
UDP_HEADER_LEN* = 8
|
||||||
|
TUNNEL_OVERHEAD* = ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN # 42 bytes
|
||||||
|
|
||||||
|
# MTU safety
|
||||||
|
MAX_UTCP_FRAME* = 1400'u16 # Safe for PPPoE/VPN
|
||||||
|
|
||||||
|
proc kprint(s: cstring) {.importc, cdecl.}
|
||||||
|
proc kprintln(s: cstring) {.importc, cdecl.}
|
||||||
|
|
||||||
|
# --- Fast Path Detection ---
|
||||||
|
|
||||||
|
proc is_utcp_tunnel*(data: ptr UncheckedArray[byte], len: uint16): bool {.exportc, cdecl.} =
|
||||||
|
## Check if packet is a UTCP tunnel packet (UDP port 9999)
|
||||||
|
## Returns true if packet should bypass LwIP
|
||||||
|
|
||||||
|
# Minimum size check: ETH(14) + IP(20) + UDP(8) = 42 bytes
|
||||||
|
if len < TUNNEL_OVERHEAD:
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Check EtherType (big-endian at offset 12-13)
|
||||||
|
let eth_type = (uint16(data[12]) shl 8) or uint16(data[13])
|
||||||
|
if eth_type != ETHERTYPE_IPV4:
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Check IP Protocol (offset 23 in frame = offset 9 in IP header)
|
||||||
|
let ip_proto = data[23]
|
||||||
|
if ip_proto != IPPROTO_UDP:
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Check UDP destination port (big-endian at offset 36-37)
|
||||||
|
# ETH(14) + IP(20) + UDP dst port offset(2) = 36
|
||||||
|
let dst_port = (uint16(data[36]) shl 8) or uint16(data[37])
|
||||||
|
|
||||||
|
return dst_port == UTCP_TUNNEL_PORT
|
||||||
|
|
||||||
|
|
||||||
|
proc strip_tunnel_headers*(data: ptr UncheckedArray[byte], len: var uint16): ptr UncheckedArray[byte] {.exportc, cdecl.} =
|
||||||
|
## Strip ETH+IP+UDP headers from tunnel packet (zero-copy)
|
||||||
|
## Returns pointer to UTCP header, adjusts length
|
||||||
|
##
|
||||||
|
## SAFETY: Caller must ensure len >= TUNNEL_OVERHEAD
|
||||||
|
|
||||||
|
if len < TUNNEL_OVERHEAD:
|
||||||
|
return nil
|
||||||
|
|
||||||
|
# Zero-copy: just advance pointer
|
||||||
|
let utcp_data = cast[ptr UncheckedArray[byte]](
|
||||||
|
cast[uint64](data) + TUNNEL_OVERHEAD
|
||||||
|
)
|
||||||
|
len = len - TUNNEL_OVERHEAD
|
||||||
|
|
||||||
|
return utcp_data
|
||||||
|
|
||||||
|
|
||||||
|
proc check_mtu*(len: uint16): bool =
|
||||||
|
## Check if UTCP frame exceeds safe MTU
|
||||||
|
return len <= MAX_UTCP_FRAME
|
||||||
|
|
||||||
|
|
||||||
|
# --- Source Address Extraction (for response routing) ---
|
||||||
|
|
||||||
|
type
|
||||||
|
TunnelSource* = object
|
||||||
|
ip*: uint32 # Source IP (network byte order)
|
||||||
|
port*: uint16 # Source port
|
||||||
|
|
||||||
|
proc extract_tunnel_source*(data: ptr UncheckedArray[byte]): TunnelSource =
|
||||||
|
## Extract source IP and port from tunnel packet for response routing
|
||||||
|
|
||||||
|
# Source IP at ETH(14) + IP src offset(12) = 26
|
||||||
|
result.ip = (uint32(data[26]) shl 24) or
|
||||||
|
(uint32(data[27]) shl 16) or
|
||||||
|
(uint32(data[28]) shl 8) or
|
||||||
|
uint32(data[29])
|
||||||
|
|
||||||
|
# Source port at ETH(14) + IP(20) + UDP src offset(0) = 34
|
||||||
|
result.port = (uint16(data[34]) shl 8) or uint16(data[35])
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
# Nexus Sovereign Core: Kernel Implementation
|
# Nexus Sovereign Core: Kernel Implementation
|
||||||
# target Bravo: Complete Build Unification
|
# target Bravo: Complete Build Unification
|
||||||
|
|
||||||
import ring, fiber, ion, sched, pty, cspace, ontology, channels
|
import ring, fiber, ion, sched, pty, cspace, ontology, channels, fastpath
|
||||||
import fs/vfs, fs/tar, fs/sfs
|
import fs/vfs, fs/tar, fs/sfs
|
||||||
import loader/elf
|
import loader/elf
|
||||||
import ../libs/membrane/term
|
import ../libs/membrane/term
|
||||||
|
|
@ -318,21 +318,36 @@ proc fiber_netswitch_entry() {.cdecl.} =
|
||||||
|
|
||||||
kprintln("[NetSwitch] Channels verified. Sovereignty confirmed.")
|
kprintln("[NetSwitch] Channels verified. Sovereignty confirmed.")
|
||||||
|
|
||||||
|
# Fast Path imports (SPEC-700)
|
||||||
|
proc is_utcp_tunnel(data: ptr UncheckedArray[byte], len: uint16): bool {.importc, cdecl.}
|
||||||
|
proc strip_tunnel_headers(data: ptr UncheckedArray[byte], len: var uint16): ptr UncheckedArray[byte] {.importc, cdecl.}
|
||||||
|
|
||||||
while true:
|
while true:
|
||||||
var pkt: IonPacket
|
var pkt: IonPacket
|
||||||
|
|
||||||
# INGRESS: Driver -> NetSwitch -> Subject (chan_net_rx)
|
# INGRESS: Driver -> NetSwitch -> Fast Path Filter
|
||||||
if chan_netswitch_rx.recv(pkt):
|
if chan_netswitch_rx.recv(pkt):
|
||||||
if not chan_net_rx.send(pkt):
|
# SPEC-700: Fast Path Bypass for UTCP Tunnel (UDP/9999)
|
||||||
# kprintln("[NetSwitch] Dropped Ingress (Drop)")
|
if is_utcp_tunnel(pkt.data, pkt.len):
|
||||||
|
# FAST PATH: Bypass LwIP entirely
|
||||||
|
var utcp_len = pkt.len
|
||||||
|
let utcp_data = strip_tunnel_headers(pkt.data, utcp_len)
|
||||||
|
if utcp_data != nil:
|
||||||
|
# TODO(UTCP): Route to UTCP handler when implemented
|
||||||
|
# For now, log and drop
|
||||||
|
kprintln("[FastPath] UTCP Tunnel packet detected - UTCP handler pending")
|
||||||
|
ion_free_raw(pkt.id)
|
||||||
|
else:
|
||||||
|
kprintln("[FastPath] Strip failed - dropping")
|
||||||
|
ion_free_raw(pkt.id)
|
||||||
|
else:
|
||||||
|
# SLOW PATH: Route to LwIP via chan_net_rx
|
||||||
|
if not chan_net_rx.send(pkt):
|
||||||
ion_free_raw(pkt.id)
|
ion_free_raw(pkt.id)
|
||||||
# else:
|
|
||||||
# kprintln("[NetSwitch] Forwarded Ingress")
|
|
||||||
|
|
||||||
# EGRESS: Subject (chan_net_tx) -> NetSwitch -> Driver (ion_tx_push)
|
# EGRESS: Subject (chan_net_tx) -> NetSwitch -> Driver (ion_tx_push)
|
||||||
if chan_net_tx.recv(pkt):
|
if chan_net_tx.recv(pkt):
|
||||||
kprintln("[NetSwitch] Forwarding Egress")
|
kprintln("[NetSwitch] Forwarding Egress")
|
||||||
# uart_print("[NetSwitch] Forwarding Egress\n")
|
|
||||||
var res = ion_tx_push(pkt)
|
var res = ion_tx_push(pkt)
|
||||||
if not res: kprintln("[NetSwitch] Drop (TX Full)")
|
if not res: kprintln("[NetSwitch] Drop (TX Full)")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -216,8 +216,6 @@ proc membrane_init*() {.exportc, cdecl.} =
|
||||||
var last_notified_ip: uint32 = 0
|
var last_notified_ip: uint32 = 0
|
||||||
var dhcp_retried = false
|
var dhcp_retried = false
|
||||||
var last_ping_time: uint32 = 0
|
var last_ping_time: uint32 = 0
|
||||||
var looped_ping_done = false
|
|
||||||
var gateway_ping_count = 0
|
|
||||||
|
|
||||||
proc glue_print_hex(v: uint64) =
|
proc glue_print_hex(v: uint64) =
|
||||||
const hex_chars = "0123456789ABCDEF"
|
const hex_chars = "0123456789ABCDEF"
|
||||||
|
|
@ -232,14 +230,9 @@ proc glue_print_hex(v: uint64) =
|
||||||
|
|
||||||
proc pump_membrane_stack*() {.exportc, cdecl.} =
|
proc pump_membrane_stack*() {.exportc, cdecl.} =
|
||||||
## The Pulse of the Membrane. Call frequently to handle timers and RX.
|
## The Pulse of the Membrane. Call frequently to handle timers and RX.
|
||||||
|
# glue_print("[Membrane] Pump\n")
|
||||||
|
|
||||||
let now = sys_now()
|
let now = sys_now()
|
||||||
# if (now mod 1000) < 50:
|
|
||||||
# glue_print(".")
|
|
||||||
|
|
||||||
# glue_print("[Membrane] Time: ")
|
|
||||||
# glue_print_hex(uint64(now))
|
|
||||||
# glue_print("\n")
|
|
||||||
|
|
||||||
# 3. Check for IP (Avoid continuous Nim string allocation/leak)
|
# 3. Check for IP (Avoid continuous Nim string allocation/leak)
|
||||||
var ip_addr: uint32
|
var ip_addr: uint32
|
||||||
|
|
@ -250,8 +243,6 @@ proc pump_membrane_stack*() {.exportc, cdecl.} =
|
||||||
glue_print("\n")
|
glue_print("\n")
|
||||||
last_notified_ip = ip_addr
|
last_notified_ip = ip_addr
|
||||||
|
|
||||||
# Force DHCP Retry if no IP after 3 seconds
|
|
||||||
|
|
||||||
# Force DHCP Retry if no IP after 3 seconds
|
# Force DHCP Retry if no IP after 3 seconds
|
||||||
if now > 3000 and not dhcp_retried and ip_addr == 0:
|
if now > 3000 and not dhcp_retried and ip_addr == 0:
|
||||||
dhcp_retried = true
|
dhcp_retried = true
|
||||||
|
|
@ -267,13 +258,17 @@ proc pump_membrane_stack*() {.exportc, cdecl.} =
|
||||||
etharp_tmr()
|
etharp_tmr()
|
||||||
last_arp_tmr = now
|
last_arp_tmr = now
|
||||||
|
|
||||||
# Phase 36c: Ping Automation (Disabled/Silent)
|
# Phase 37a: ICMP Ping Verification
|
||||||
if now - last_ping_time > 5000:
|
if now - last_ping_time > 1000:
|
||||||
last_ping_time = now
|
last_ping_time = now
|
||||||
|
|
||||||
if ip_addr != 0:
|
if ip_addr != 0:
|
||||||
# Trigger periodic actions here
|
glue_print("[Membrane] PING: Sending ICMP Echo...\n")
|
||||||
discard
|
{.emit: """
|
||||||
# {.emit: "ip_addr_t t; IP4_ADDR(&t, 10,0,2,2); ping_send(&t);".}
|
ip_addr_t gateway;
|
||||||
|
IP4_ADDR(&gateway, 10, 0, 2, 2);
|
||||||
|
ping_send(&gateway);
|
||||||
|
""".}
|
||||||
|
|
||||||
|
|
||||||
# DHCP Timers
|
# DHCP Timers
|
||||||
|
|
@ -288,8 +283,9 @@ proc pump_membrane_stack*() {.exportc, cdecl.} =
|
||||||
|
|
||||||
# 2. RX Ingress
|
# 2. RX Ingress
|
||||||
var pkt: IonPacket
|
var pkt: IonPacket
|
||||||
|
# glue_print("[Membrane] Exit Pump\n")
|
||||||
while ion_net_rx(addr pkt):
|
while ion_net_rx(addr pkt):
|
||||||
glue_print("[Membrane] Ingress Packet\n")
|
# glue_print("[Membrane] Ingress Packet\n")
|
||||||
# DEBUG: Hex dump first 32 bytes (Disabled for Ping Test)
|
# DEBUG: Hex dump first 32 bytes (Disabled for Ping Test)
|
||||||
# {.emit: """
|
# {.emit: """
|
||||||
# printf("[Membrane] RX Hex Dump (first 32 bytes):\n");
|
# printf("[Membrane] RX Hex Dump (first 32 bytes):\n");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue