# 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 "lwip/raw.h" #include "lwip/icmp.h" #include "lwip/inet_chksum.h" #include #include "lwip/dhcp.h" #include "lwip/dns.h" // Externs extern int printf(const char *format, ...); """.} proc lwip_init*() {.importc: "lwip_init", cdecl.} proc dns_init*() {.importc: "dns_init", cdecl.} proc dns_tmr*() {.importc: "dns_tmr", 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.} {.emit: """ // --- PING IMPLEMENTATION --- static struct raw_pcb *ping_pcb; static u16_t ping_seq_num; const char* lwip_strerr(err_t err) { return "LwIP Error"; } static u8_t ping_recv(void *arg, struct raw_pcb *pcb, struct pbuf *p, const ip_addr_t *addr) { LWIP_UNUSED_ARG(arg); LWIP_UNUSED_ARG(pcb); if (p->tot_len >= sizeof(struct ip_hdr) + sizeof(struct icmp_echo_hdr)) { printf("[Membrane] PING REPLY from %s: %d bytes\n", ipaddr_ntoa(addr), p->tot_len); } pbuf_free(p); return 1; // Eat the packet } void ping_send(const ip_addr_t *addr) { if (!ping_pcb) { ping_pcb = raw_new(IP_PROTO_ICMP); if (ping_pcb) { raw_recv(ping_pcb, ping_recv, NULL); raw_bind(ping_pcb, IP_ADDR_ANY); } } if (!ping_pcb) return; struct pbuf *p = pbuf_alloc(PBUF_IP, sizeof(struct icmp_echo_hdr) + 32, PBUF_RAM); if (!p) return; struct icmp_echo_hdr *iecho = (struct icmp_echo_hdr *)p->payload; ICMPH_TYPE_SET(iecho, ICMP_ECHO); ICMPH_CODE_SET(iecho, 0); iecho->chksum = 0; iecho->id = 0xAFAF; iecho->seqno = lwip_htons(++ping_seq_num); // Fill payload memset((char *)p->payload + sizeof(struct icmp_echo_hdr), 'A', 32); iecho->chksum = inet_chksum(iecho, p->len); raw_sendto(ping_pcb, p, addr); pbuf_free(p); } """.} # ... (Types and ION hooks) ... 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 # LwIP provides complete Ethernet frames (14-byte header + payload) # VirtIO-net requires 12-byte header at start of buffer (Modern with MRG_RXBUF) var offset = 12 # Start after VirtIO header space {.emit: """ struct pbuf *curr = (struct pbuf *)`p`; while (curr != NULL) { if (`offset` + curr->len > 2000) break; // Copy Ethernet frame directly (includes header) memcpy((void*)((uintptr_t)`pkt`.data + `offset`), curr->payload, curr->len); `offset` += curr->len; curr = curr->next; } """.} # Zero out VirtIO-net header (first 12 bytes - Modern with MRG_RXBUF) {.emit: """ memset((void*)`pkt`.data, 0, 12); """.} pkt.len = uint16(offset) # Total: 12 (VirtIO) + Ethernet frame 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.} = let mac = ion_get_mac() glue_print("[Membrane] Configuring Interface with Hardware MAC\n") {.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 from SysTable ni->hwaddr[0] = `mac`[0]; ni->hwaddr[1] = `mac`[1]; ni->hwaddr[2] = `mac`[2]; ni->hwaddr[3] = `mac`[3]; ni->hwaddr[4] = `mac`[4]; ni->hwaddr[5] = `mac`[5]; """.} return 0 # --- Membrane Globals --- var g_netif: pointer var last_tcp_tmr, last_arp_tmr, last_dhcp_fine, last_dhcp_coarse, last_dns_tmr: uint32 var membrane_started = false proc membrane_init*() {.exportc, cdecl.} = if membrane_started: return membrane_started = true let now = sys_now() last_tcp_tmr = now last_arp_tmr = now last_dhcp_fine = now last_dhcp_coarse = now last_dns_tmr = now glue_print("[Membrane] Initialization...\n") ion_user_init() # 1. LwIP Stack Init glue_print("[Membrane] Calling lwip_init()...\n") lwip_init() dns_init() # Initialize DNS resolver # Set Fallback DNS (8.8.8.8) {.emit: """ static ip_addr_t dns_server; IP4_ADDR(ip_2_ip4(&dns_server), 8, 8, 8, 8); dns_setserver(0, &dns_server); """.} glue_print("[Membrane] lwip_init() returned. DNS Initialized.\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") proc glue_get_ip*(): uint32 {.exportc, cdecl.} = ## Returns current IP address in host byte order {.emit: "return ip4_addr_get_u32(netif_ip4_addr((struct netif *)`g_netif`));".} var last_notified_ip: uint32 = 0 var last_ping_time: 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' 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() # 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: ") glue_print_hex(uint64(ip_addr)) glue_print("\n") last_notified_ip = ip_addr # 1. LwIP Timers (Raw API needs manual polling) {.emit: """ static int debug_tick = 0; if (debug_tick++ % 200 == 0) { printf("[Membrane] sys_now: %u\n", `now`); } """.} 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: 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 # DNS Timer (every 1000ms) if now - last_dns_tmr >= 1000: dns_tmr() last_dns_tmr = now # Phase 37a: ICMP Ping Verification if now - last_ping_time > 1000: last_ping_time = now if ip_addr != 0: glue_print("[Membrane] PING: Sending ICMP Echo...\n") {.emit: """ ip_addr_t gateway; IP4_ADDR(&gateway, 10, 0, 2, 2); ping_send(&gateway); """.} # 2. RX Ingress var pkt: IonPacket # glue_print("[Membrane] Exit Pump\n") while ion_net_rx(addr pkt): # glue_print("[Membrane] Ingress Packet\n") # DEBUG: Hex dump first 32 bytes (Disabled for Ping Test) # {.emit: """ # printf("[Membrane] RX Hex Dump (first 32 bytes):\n"); # for (int i = 0; i < 32 && i < `pkt`.len; i++) { # printf("%02x ", `pkt`.data[i]); # if ((i + 1) % 16 == 0) printf("\n"); # } # printf("\n"); # """.} # Pass to LwIP {.emit: """ struct pbuf *p = pbuf_alloc(PBUF_RAW, `pkt`.len, PBUF_POOL); if (p != NULL) { if (`pkt`.data == NULL) { printf("[Membrane] ERROR: Ingress pkt.data is NULL!\n"); pbuf_free(p); } else { pbuf_take(p, (void*)((uintptr_t)`pkt`.data), `pkt`.len); if (netif_default->input(p, netif_default) != ERR_OK) { pbuf_free(p); } } } else { printf("[Membrane] CRITICAL: pbuf_alloc FAILED! (POOL OOM?)\n"); } """.} 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 # --- DNS GLUE (C Implementation) --- {.emit: """ static ip_addr_t g_dns_ip; static int g_dns_status = 0; // 0=idle, 1=pending, 2=done, -1=error static void my_dns_callback(const char *name, const ip_addr_t *ipaddr, void *callback_arg) { if (ipaddr != NULL) { g_dns_ip = *ipaddr; g_dns_status = 2; // Success } else { g_dns_status = -1; // Error } } // Check if DNS is properly initialized int glue_dns_check_init(void) { // We can't directly access dns_pcbs[] as it's static in dns.c // Instead, we'll try to get the DNS server, which will fail if DNS isn't init'd const ip_addr_t *ns = dns_getserver(0); if (ns == NULL) { printf("[Membrane] DNS ERROR: dns_getserver returned NULL\\n"); return -1; } // If we got here, DNS subsystem is at least partially initialized return 0; } int glue_resolve_start(char* hostname) { ip_addr_t ip; err_t err; g_dns_status = 1; // Pending default // Ensure we have a DNS server const ip_addr_t *ns = dns_getserver(0); if (ns == NULL || ip_addr_isany(ns)) { printf("[Membrane] No DNS server available. Using fallback 8.8.8.8\n"); static ip_addr_t fallback; IP_ADDR4(&fallback, 8, 8, 8, 8); dns_setserver(0, &fallback); ns = dns_getserver(0); } printf("[Membrane] Resolving '%s' via DNS: %s\n", hostname, ipaddr_ntoa(ns)); err = dns_gethostbyname(hostname, &ip, my_dns_callback, NULL); if (err == ERR_OK) { g_dns_ip = ip; g_dns_status = 2; // Done return 0; } else if (err == ERR_INPROGRESS) { return 1; } else { printf("[Membrane] dns_gethostbyname FAILED with error: %d\n", (int)err); g_dns_status = -1; return -1; } } int glue_resolve_check(u32_t *ip_out) { if (g_dns_status == 1) return 1; if (g_dns_status == 2) { *ip_out = ip4_addr_get_u32(&g_dns_ip); return 0; } return -1; } """.}