rumpk/libs/membrane/net_glue.nim

412 lines
12 KiB
Nim

# 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.
## Nexus Membrane: Network Transport Glue
# MARKUS MAIWALD (ARCHITECT) | VOXIS FORGE (AI)
# Rumpk Phase 36: Membrane Networking (Userland High-Speed IO)
import ion_client
# NOTE: Do NOT import ../../core/ion - it pulls in the KERNEL-ONLY 2MB memory pool!
proc console_write(s: pointer, len: csize_t) {.importc: "console_write", cdecl.}
proc glue_print(s: string) =
console_write(unsafeAddr s[0], csize_t(s.len))
# LwIP Imports
{.passC: "-Icore/rumpk/vendor/lwip/src/include".}
{.passC: "-Icore/rumpk/libs/membrane/include".}
# --- LwIP C Definitions ---
# Since we are building with 'any' OS and freestanding, we use Emit for C structural access
# but provide Nim wrappers for logic.
{.emit: """
#include "lwip/init.h"
#include "lwip/netif.h"
#include "lwip/pbuf.h"
#include "lwip/etharp.h"
#include "lwip/tcp.h"
#include "lwip/timeouts.h"
#include "netif/ethernet.h"
#include <string.h>
#include "lwip/dhcp.h"
// If string.h is missing, we need the prototype for our clib.c implementation
void* memcpy(void* dest, const void* src, size_t n);
extern err_t etharp_output(struct netif *netif, struct pbuf *p, const ip4_addr_t *ipaddr);
""".}
proc lwip_init*() {.importc: "lwip_init", cdecl.}
proc etharp_tmr*() {.importc: "etharp_tmr", cdecl.}
proc tcp_tmr*() {.importc: "tcp_tmr", cdecl.}
proc dhcp_fine_tmr() {.importc: "dhcp_fine_tmr", cdecl.}
proc dhcp_coarse_tmr() {.importc: "dhcp_coarse_tmr", cdecl.}
proc sys_now*(): uint32 {.importc: "sys_now", cdecl.}
type
SockState* = enum
CLOSED, LISTEN, CONNECTING, ESTABLISHED, FIN_WAIT
NexusSock* = object
fd*: int
pcb*: pointer
state*: SockState
rx_buf*: array[4096, byte]
rx_len*: int
# Forward declarations for LwIP callbacks
proc ion_linkoutput(netif: pointer, p: pointer): int32 {.exportc, cdecl.} =
## Callback: LwIP -> Netif -> ION Ring
glue_print("[Membrane] Egress Packet\n")
var pkt: IonPacket
if not ion_user_alloc(addr pkt):
return -1 # ERR_MEM
# Copy pbuf chain into a single ION slab
var offset = 0
{.emit: """
struct pbuf *curr = (struct pbuf *)`p`;
while (curr != NULL) {
if (`offset` + curr->len > 2000) break;
memcpy((void*)((uintptr_t)`pkt`.data + `offset`), curr->payload, curr->len);
`offset` += curr->len;
curr = curr->next;
}
""".}
pkt.len = uint16(offset)
if not ion_net_tx(pkt):
ion_user_free(pkt)
return -1 # ERR_IF
return 0 # ERR_OK
proc ion_netif_init(netif: pointer): int32 {.exportc, cdecl.} =
{.emit: """
struct netif *ni = (struct netif *)`netif`;
ni->name[0] = 'i';
ni->name[1] = 'o';
ni->output = etharp_output;
ni->linkoutput = (netif_linkoutput_fn)ion_linkoutput;
ni->mtu = 1500;
ni->hwaddr_len = 6;
ni->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET | NETIF_FLAG_LINK_UP;
// Set MAC: 00:DE:AD:BE:EF:01 (matching QEMU -netdev tap)
ni->hwaddr[0] = 0x00; ni->hwaddr[1] = 0xDE; ni->hwaddr[2] = 0xAD;
ni->hwaddr[3] = 0xBE; ni->hwaddr[4] = 0xEF; ni->hwaddr[5] = 0x01;
""".}
return 0
# --- Membrane Globals ---
var g_netif: pointer
var last_tcp_tmr, last_arp_tmr, last_dhcp_fine, last_dhcp_coarse: uint32
var membrane_started = false
proc membrane_init*() {.exportc, cdecl.} =
if membrane_started: return
membrane_started = true
glue_print("[Membrane] Initialization...\n")
ion_user_init()
# 1. LwIP Stack Init
glue_print("[Membrane] Calling lwip_init()...\n")
lwip_init()
glue_print("[Membrane] lwip_init() returned.\n")
# 2. Setup Netif
{.emit: """
static struct netif ni_static;
ip4_addr_t ip, mask, gw;
// Phase 38: DHCP Enabled
IP4_ADDR(&ip, 0, 0, 0, 0);
IP4_ADDR(&mask, 0, 0, 0, 0);
IP4_ADDR(&gw, 0, 0, 0, 0);
netif_add(&ni_static, &ip, &mask, &gw, NULL, (netif_init_fn)ion_netif_init, (netif_input_fn)ethernet_input);
netif_set_default(&ni_static);
netif_set_up(&ni_static);
dhcp_start(&ni_static);
`g_netif` = &ni_static;
""".}
glue_print("[Membrane] Network Stack Operational (Waiting for DHCP IP...)\n")
var last_notified_ip: uint32 = 0
# proc glue_print_hex(v: uint64) =
# const hex_chars = "0123456789ABCDEF"
# var buf: array[20, char]
# buf[0] = '0'; buf[1] = 'x'
# var val = v
# for i in countdown(15, 0):
# buf[2+i] = hex_chars[int(val and 0xF)]
# val = val shr 4
# buf[18] = '\n'; buf[19] = '\0'
# buf[18] = '\n'; buf[19] = '\0'
# console_write(addr buf[0], 20)
proc pump_membrane_stack*() {.exportc, cdecl.} =
## The Pulse of the Membrane. Call frequently to handle timers and RX.
let now = sys_now()
# glue_print("[Membrane] Time: ")
# glue_print_hex(uint64(now))
# 3. Check for IP (Avoid continuous Nim string allocation/leak)
var ip_addr: uint32
{.emit: "`ip_addr` = ip4_addr_get_u32(netif_ip4_addr((struct netif *)`g_netif`));".}
if ip_addr != 0 and ip_addr != last_notified_ip:
glue_print("[Membrane] IP STATUS CHANGE: ")
# Call Zig kprint_hex directly
proc kprint_hex_ext(v: uint64) {.importc: "kprint_hex", cdecl.}
kprint_hex_ext(uint64(ip_addr))
glue_print("\n")
last_notified_ip = ip_addr
# 1. LwIP Timers (Raw API needs manual polling)
if now - last_tcp_tmr >= 250:
tcp_tmr()
last_tcp_tmr = now
if now - last_arp_tmr >= 5000:
etharp_tmr()
last_arp_tmr = now
# DHCP Timers
if now - last_dhcp_fine >= 500:
# glue_print("[Membrane] DHCP Fine Timer\n")
dhcp_fine_tmr()
last_dhcp_fine = now
if now - last_dhcp_coarse >= 60000:
glue_print("[Membrane] DHCP Coarse Timer\n")
dhcp_coarse_tmr()
last_dhcp_coarse = now
# 2. RX Ingress
var pkt: IonPacket
while ion_net_rx(addr pkt):
glue_print("[Membrane] Ingress Packet\n")
# Pass to LwIP
{.emit: """
struct pbuf *p = pbuf_alloc(PBUF_RAW, `pkt`.len, PBUF_POOL);
if (p != NULL) {
pbuf_take(p, `pkt`.data, `pkt`.len);
if (netif_default->input(p, netif_default) != ERR_OK) {
pbuf_free(p);
}
}
""".}
ion_user_free(pkt)
# --- Glue Stubs (Phase 37) ---
# --- Glue Implementation (Phase 38) ---
# --- Glue Implementation (Phase 40) ---
# Global C definition for NexusSock to ensure visibility
{.emit: """
typedef struct {
NI fd;
void* pcb;
int state; // 0=CLOSED, 1=LISTEN, 2=CONNECTING, 3=ESTABLISHED
unsigned char rx_buf[4096];
NI rx_len;
// Server Fields
void* accepted_pcb;
int accepted_pending;
} NexusSock_C;
""".}
proc ion_tcp_connected(arg: pointer, pcb: pointer, err: int8): int8 {.exportc, cdecl.} =
glue_print("[Membrane] TCP Connected!\n")
{.emit: """
NexusSock_C *s = (NexusSock_C *)`arg`;
s->state = 3; // ESTABLISHED
""".}
return 0
proc ion_tcp_sent(arg: pointer, pcb: pointer, len: uint16): int8 {.exportc, cdecl.} =
return 0
proc ion_tcp_recv(arg: pointer, pcb: pointer, p: pointer, err: int8): int8 {.exportc, cdecl.} =
if p == nil:
glue_print("[Membrane] TCP Closed by remote\n")
{.emit: """
NexusSock_C *s = (NexusSock_C *)`arg`;
s->state = 0; // CLOSED
""".}
return 0
# Append data to rx_buf
{.emit: """
struct pbuf *curr = (struct pbuf *)`p`;
NexusSock_C *s = (NexusSock_C *)`arg`;
if (curr != NULL) {
struct pbuf *q;
for (q = curr; q != NULL; q = q->next) {
if (s->rx_len + q->len < 4096) {
memcpy(&s->rx_buf[s->rx_len], q->payload, q->len);
s->rx_len += q->len;
}
}
pbuf_free(curr);
// We must cast pcb to struct tcp_pcb* for the macro/function
tcp_recved((struct tcp_pcb *)`pcb`, curr->tot_len);
}
""".}
return 0
proc ion_tcp_accept(arg: pointer, new_pcb: pointer, err: int8): int8 {.exportc, cdecl.} =
# Callback when a listening socket receives a new connection
{.emit: """
NexusSock_C *listener = (NexusSock_C *)`arg`;
// Store the new PCB in the backlog slot
// MVP: Only 1 pending connection allowed
if (listener->accepted_pending == 0) {
listener->accepted_pcb = `new_pcb`;
listener->accepted_pending = 1;
// Increase reference count? No, LwIP gives us ownership.
// Important: We must not set callbacks yet?
// LwIP doc: "When a new connection arrives, the accept callback function is called.
// The new pcb is passed as a parameter."
// We'll set callbacks later when libc performs the accept() syscall.
} else {
// Backlog full, reject?
tcp_abort((struct tcp_pcb *)`new_pcb`);
return -1; // ERR_ABRT
}
""".}
return 0
proc glue_setup_socket*(sock: ptr NexusSock, pcb_ptr: pointer) {.exportc, cdecl.} =
# Wire LwIP callbacks to a NexusSock
{.emit: """
NexusSock_C *ns = (NexusSock_C *)`sock`;
struct tcp_pcb *pcb = (struct tcp_pcb *)`pcb_ptr`;
ns->pcb = pcb;
tcp_arg(pcb, ns);
tcp_recv(pcb, (tcp_recv_fn)ion_tcp_recv);
tcp_sent(pcb, (tcp_sent_fn)ion_tcp_sent);
// tcp_poll(pcb, ...);
""".}
proc glue_connect*(sock: ptr NexusSock, ip: uint32, port: uint16): int {.exportc, cdecl.} =
glue_print("[Membrane] glue_connect called\n")
sock.state = CONNECTING
sock.rx_len = 0
{.emit: """
struct tcp_pcb *pcb = tcp_new();
if (pcb == NULL) return -1;
// Wire up
glue_setup_socket(`sock`, pcb);
ip4_addr_t remote_ip;
remote_ip.addr = `ip`;
tcp_connect(pcb, &remote_ip, `port`, (tcp_connected_fn)ion_tcp_connected);
""".}
return 0
proc glue_bind*(sock: ptr NexusSock, port: uint16): int {.exportc, cdecl.} =
sock.state = LISTEN # Pre-state
{.emit: """
struct tcp_pcb *pcb = tcp_new();
if (pcb == NULL) return -1;
// Bind to ANY
if (tcp_bind(pcb, IP_ADDR_ANY, `port`) != ERR_OK) {
memp_free(MEMP_TCP_PCB, pcb);
return -1;
}
// Update sock
// glue_setup_socket checks validity, but here we just need to store PCB
// Because we are not connecting, we don't set recv/sent yet?
// Actually we need tcp_arg for accept callback.
NexusSock_C *ns = (NexusSock_C *)`sock`;
ns->pcb = pcb;
tcp_arg(pcb, ns);
""".}
return 0
proc glue_listen*(sock: ptr NexusSock): int {.exportc, cdecl.} =
{.emit: """
NexusSock_C *ns = (NexusSock_C *)`sock`;
struct tcp_pcb *lpcb = tcp_listen((struct tcp_pcb *)ns->pcb);
if (lpcb == NULL) return -1;
ns->pcb = lpcb; // Update to listening PCB
tcp_accept(lpcb, (tcp_accept_fn)ion_tcp_accept);
""".}
return 0
proc glue_accept_peek*(sock: ptr NexusSock): pointer {.exportc, cdecl.} =
# Returns new PCB if pending, else nil
var p: pointer = nil
{.emit: """
NexusSock_C *ns = (NexusSock_C *)`sock`;
if (ns->accepted_pending) {
`p` = ns->accepted_pcb;
ns->accepted_pending = 0;
ns->accepted_pcb = NULL;
}
""".}
return p
proc glue_write*(sock: ptr NexusSock, buf: pointer, len: int): int {.exportc, cdecl.} =
{.emit: """
struct tcp_pcb *pcb = (struct tcp_pcb *)`sock`->pcb;
if (pcb == NULL) return -1;
tcp_write(pcb, `buf`, `len`, TCP_WRITE_FLAG_COPY);
tcp_output(pcb);
""".}
return len
proc glue_read*(sock: ptr NexusSock, buf: pointer, len: int): int {.exportc, cdecl.} =
if sock.rx_len == 0: return 0
var to_read = len
if to_read > sock.rx_len: to_read = sock.rx_len
copyMem(buf, addr sock.rx_buf[0], uint64(to_read))
# Shift buffer (Ring buffer would be better, but this is MVP)
var remaining = sock.rx_len - to_read
if remaining > 0:
copyMem(addr sock.rx_buf[0], addr sock.rx_buf[to_read], uint64(remaining))
sock.rx_len = remaining
return to_read
proc glue_close*(sock: ptr NexusSock): int {.exportc, cdecl.} =
{.emit: """
struct tcp_pcb *pcb = (struct tcp_pcb *)`sock`->pcb;
if (pcb != NULL) {
tcp_close(pcb);
`sock`->pcb = NULL;
}
""".}
return 0