rumpk/core/net.nim

226 lines
7.0 KiB
Nim

# Rumpk LwIP Bridge
# FFI wrapper for LwIP static library
import ring
proc console_write(p: pointer, len: csize_t) {.importc, cdecl.}
const
MAX_PACKET_SIZE = 1536
RING_SIZE = 256
type
LwipErr* = int8
const
ERR_OK* = 0
ERR_MEM* = -1
ERR_BUF* = -2
ERR_TIMEOUT* = -3
ERR_RTE* = -4
ERR_INPROGRESS* = -5
ERR_VAL* = -6
ERR_WOULDBLOCK* = -7
ERR_USE* = -8
ERR_ALREADY* = -9
ERR_ISCONN* = -10
ERR_CONN* = -11
ERR_IF* = -12
ERR_ABRT* = -13
ERR_RST* = -14
ERR_CLSD* = -15
ERR_ARG* = -16
type
PbufLayer* = cint
PbufType* = cint
Pbuf* {.importc: "struct pbuf", header: "lwip/pbuf.h",
incompleteStruct.} = object
next*: ptr Pbuf
payload*: pointer
tot_len*: uint16
len*: uint16
IpAddr* {.importc: "ip4_addr_t", header: "lwip/ip_addr.h".} = object
addr_val* {.importc: "addr".}: uint32
NetIf* {.importc: "struct netif", header: "lwip/netif.h",
incompleteStruct.} = object
next*: ptr NetIf
ip_addr*, netmask*, gw*: IpAddr
var
PBUF_RAW* {.importc: "PBUF_RAW", header: "lwip/pbuf.h", nodecl.}: cint
PBUF_RAM* {.importc: "PBUF_RAM", header: "lwip/pbuf.h", nodecl.}: cint
PBUF_POOL* {.importc: "PBUF_POOL", header: "lwip/pbuf.h", nodecl.}: cint
type
NetPacket* = object
len*: int
data*: array[MAX_PACKET_SIZE, byte]
var netRing*: RingBuffer[NetPacket, RING_SIZE]
var rumpk_netif*: NetIf
var ip, mask, gw: IpAddr
# FFI Procedures
proc lwip_init*() {.importc: "lwip_init", header: "lwip/init.h".}
proc netif_add*(netif: ptr NetIf; ipaddr, netmask, gw: ptr IpAddr; state: pointer;
init: proc(netif: ptr NetIf): LwipErr {.cdecl.};
input: proc(p: ptr Pbuf; netif: ptr NetIf): LwipErr {.cdecl.}): ptr NetIf {.importc: "netif_add",
header: "lwip/netif.h".}
proc netif_set_up*(netif: ptr NetIf) {.importc: "netif_set_up",
header: "lwip/netif.h".}
proc netif_set_link_up*(netif: ptr NetIf) {.importc: "netif_set_link_up",
header: "lwip/netif.h".}
proc netif_set_default*(netif: ptr NetIf) {.importc: "netif_set_default",
header: "lwip/netif.h".}
proc sys_check_timeouts() {.importc: "sys_check_timeouts",
header: "lwip/timeouts.h".}
proc pbuf_alloc*(layer: PbufLayer; length: uint16;
ptype: PbufType): ptr Pbuf {.importc: "pbuf_alloc", header: "lwip/pbuf.h".}
proc pbuf_free*(p: ptr Pbuf): uint8 {.importc: "pbuf_free",
header: "lwip/pbuf.h".}
proc ethernet_input*(p: ptr Pbuf; netif: ptr NetIf): LwipErr {.importc: "ethernet_input",
header: "netif/ethernet.h".}
# Sovereign Integration (HAL)
proc virtio_net_poll() {.importc: "virtio_net_poll", cdecl.}
proc virtio_net_send(data: pointer; len: csize_t) {.importc: "virtio_net_send", cdecl.}
# Architecture Agnostic IP Packing
func htonl(x: uint32): uint32 =
when cpuEndian == littleEndian:
(x shr 24) or ((x shr 8) and 0xff00) or ((x shl 8) and 0xff0000) or (x shl 24)
else: x
func packIp(a, b, c, d: uint8): uint32 =
uint32(a) shl 24 or uint32(b) shl 16 or uint32(c) shl 8 or uint32(d)
proc link_output(netif: ptr NetIf; p: ptr Pbuf): LwipErr {.cdecl.} =
## Callback from LwIP when a packet needs to be sent
if p != nil:
# Phase 7: We send the whole packet in one go.
virtio_net_send(p.payload, csize_t(p.tot_len))
return ERR_OK
return ERR_VAL
proc net_driver_init*(netif: ptr NetIf): LwipErr {.cdecl.} =
# Driver initialization hook
{.emit: """
((struct netif*)`netif`)->mtu = 1500;
((struct netif*)`netif`)->hwaddr_len = 6;
((struct netif*)`netif`)->hwaddr[0] = 0x52;
((struct netif*)`netif`)->hwaddr[1] = 0x54;
((struct netif*)`netif`)->hwaddr[2] = 0x00;
((struct netif*)`netif`)->hwaddr[3] = 0x12;
((struct netif*)`netif`)->hwaddr[4] = 0x34;
((struct netif*)`netif`)->hwaddr[5] = 0x56;
// NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP | NETIF_FLAG_ETHERNET
((struct netif*)`netif`)->flags = 0x02 | 0x08 | 0x10 | 0x40;
// Wire Tx output
((struct netif*)`netif`)->linkoutput = `link_output`;
""".}
return ERR_OK
proc net_init*() =
## Initialize the networking subsystem
netRing.init()
lwip_init()
# QEMU User Networking Defaults (10.0.2.15)
ip.addr_val = htonl(packIp(10, 0, 2, 15))
mask.addr_val = htonl(packIp(255, 255, 255, 0))
gw.addr_val = htonl(packIp(10, 0, 2, 2))
# Register the Interface
discard netif_add(addr rumpk_netif, addr ip, addr mask, addr gw,
nil, net_driver_init,
cast[proc(p: ptr Pbuf;
netif: ptr NetIf): LwipErr {.cdecl.}](ethernet_input))
# Bring it UP
netif_set_up(addr rumpk_netif)
netif_set_link_up(addr rumpk_netif)
netif_set_default(addr rumpk_netif)
# Force Gratuitous ARP to announce our MAC to the network/SLIRP
proc etharp_gratuitous(netif: ptr NetIf) {.importc: "etharp_gratuitous",
header: "netif/etharp.h".}
etharp_gratuitous(addr rumpk_netif)
proc net_ingest_packet*(data: ptr byte; len: int): bool {.exportc.} =
## Ingest a raw ethernet frame from the hardware
var pkt: NetPacket
if len > MAX_PACKET_SIZE: return false
pkt.len = len
copyMem(addr pkt.data[0], data, len)
return netRing.push(pkt)
proc fiber_yield() =
# Don't use WFI - it blocks without interrupts configured
# Use simple spin-loop for now
for i in 0..100:
{.emit: "asm volatile(\"nop\");".}
proc net_loop_cycle*(netif: ptr NetIf) =
## One cycle of the networking logic (Poll + Drain + LwIP)
# 1. Poll Hardware
virtio_net_poll()
# 2. Drain the Ring
var work_done = false
while not netRing.isEmpty:
let (ok, pkt) = netRing.pop()
if ok:
work_done = true
let p = pbuf_alloc(PBUF_RAW, uint16(pkt.len), PBUF_POOL)
if p != nil:
copyMem(p.payload, unsafeAddr pkt.data[0], pkt.len)
let err = ethernet_input(p, netif)
if err != ERR_OK:
var msg = "[Net] ethernet_input failed\n"
console_write(addr msg[0], csize_t(msg.len))
discard pbuf_free(p)
else:
var msg = "[Net] pbuf_alloc FAILED (OOM?)\n"
console_write(addr msg[0], csize_t(msg.len))
# 3. LwIP Maintenance
sys_check_timeouts()
# 4. Periodic Activity (Every 5000 loops)
const PERIOD = 5000
var counter {.global.} = 0
counter += 1
if counter >= PERIOD:
counter = 0
# Send UDP Heartbeat to Gateway
var frame: array[64, byte]
# (Simplified frame construction for clarity)
for i in 0..63: frame[i] = 0
frame[0] = 0x52; frame[1] = 0x55; frame[2] = 0x0A; frame[3] = 0x00; frame[
4] = 0x02; frame[5] = 0x02 # Dst
frame[6] = 0x52; frame[7] = 0x54; frame[8] = 0x00; frame[9] = 0x12; frame[
10] = 0x34; frame[11] = 0x56 # Src
frame[12] = 0x08; frame[13] = 0x00 # IPv4
frame[14] = 0x45; frame[23] = 17 # UDP
frame[26] = 10; frame[27] = 0; frame[28] = 2; frame[29] = 15 # Src IP
frame[30] = 10; frame[31] = 0; frame[32] = 2; frame[33] = 2 # Dst IP
frame[34] = 0x1F; frame[35] = 0x90; frame[36] = 0x1F; frame[37] = 0x90 # Ports 8080
frame[42] = ord('H').byte; frame[43] = ord('I').byte
virtio_net_send(addr frame[0], 64)
# 5. Yield / Governor
if not work_done:
fiber_yield()