diff --git a/GNUmakefile b/GNUmakefile index 212ca3d..9f71b6c 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -31,18 +31,24 @@ run-hdd: run-hdd-$(ARCH) .PHONY: run-x86_64 run-x86_64: $(IMAGE_NAME).iso + sudo ./scripts/net-setup.sh qemu-system-$(ARCH) \ -M q35 \ -bios /usr/share/ovmf/OVMF.fd \ -cdrom $(IMAGE_NAME).iso \ + -device e1000,netdev=net0,mac=52:54:00:68:00:99 \ + -netdev tap,id=net0,ifname=tap0,script=no,downscript=no \ $(QEMUFLAGS) .PHONY: run-hdd-x86_64 run-hdd-x86_64: $(IMAGE_NAME).hdd + sudo ./scripts/net-setup.sh qemu-system-$(ARCH) \ -M q35 \ -bios /usr/share/ovmf/OVMF.fd \ -hda $(IMAGE_NAME).hdd \ + -device e1000,netdev=net0,mac=52:54:00:68:00:99 \ + -netdev tap,id=net0,ifname=tap0,script=no,downscript=no \ $(QEMUFLAGS) .PHONY: run-aarch64 @@ -126,17 +132,23 @@ run-hdd-loongarch64: $(IMAGE_NAME).hdd .PHONY: run-bios run-bios: $(IMAGE_NAME).iso + sudo ./scripts/net-setup.sh qemu-system-$(ARCH) \ -M q35 \ -cdrom $(IMAGE_NAME).iso \ -boot d \ + -device e1000,netdev=net0,mac=52:54:00:68:00:99 \ + -netdev tap,id=net0,ifname=tap0,script=no,downscript=no \ $(QEMUFLAGS) .PHONY: run-hdd-bios run-hdd-bios: $(IMAGE_NAME).hdd + sudo ./scripts/net-setup.sh qemu-system-$(ARCH) \ -M q35 \ -hda $(IMAGE_NAME).hdd \ + -device e1000,netdev=net0,mac=52:54:00:68:00:99 \ + -netdev tap,id=net0,ifname=tap0,script=no,downscript=no \ $(QEMUFLAGS) .PHONY: toolchain diff --git a/kernel/src/Drivers/Net/E1000.cpp b/kernel/src/Drivers/Net/E1000.cpp new file mode 100644 index 0000000..b2c5163 --- /dev/null +++ b/kernel/src/Drivers/Net/E1000.cpp @@ -0,0 +1,446 @@ +/* + * E1000.cpp + * Intel 82540EM (E1000) Ethernet driver + * Copyright (c) 2025 Daniel Hammer +*/ + +#include "E1000.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Kt; + +namespace Drivers::Net::E1000 { + + // PCI vendor/device IDs for the Intel 82540EM + static constexpr uint16_t VendorIntel = 0x8086; + static constexpr uint16_t DeviceE1000 = 0x100E; + + // PCI config space offsets + static constexpr uint8_t PCI_REG_BAR0 = 0x10; + static constexpr uint8_t PCI_REG_COMMAND = 0x04; + static constexpr uint8_t PCI_REG_INTERRUPT = 0x3C; + + // PCI command register bits + static constexpr uint16_t PCI_CMD_BUS_MASTER = (1 << 2); + static constexpr uint16_t PCI_CMD_MEM_SPACE = (1 << 1); + + // Driver state + static bool g_initialized = false; + static volatile uint8_t* g_mmioBase = nullptr; + static uint8_t g_macAddress[6] = {}; + static uint8_t g_irqLine = 0; + + // Descriptor rings (physical addresses for DMA, virtual for CPU access) + static RxDescriptor* g_rxDescs = nullptr; + static TxDescriptor* g_txDescs = nullptr; + static uint64_t g_rxDescsPhys = 0; + static uint64_t g_txDescsPhys = 0; + + // Packet buffers (virtual addresses) + static uint8_t* g_rxBuffers[RX_DESC_COUNT] = {}; + static uint8_t* g_txBuffers[TX_DESC_COUNT] = {}; + static uint64_t g_rxBuffersPhys[RX_DESC_COUNT] = {}; + static uint64_t g_txBuffersPhys[TX_DESC_COUNT] = {}; + + // Current descriptor indices + static uint32_t g_rxTail = 0; + static uint32_t g_txTail = 0; + + // Statistics + static uint64_t g_rxPacketCount = 0; + static uint64_t g_txPacketCount = 0; + + // RX callback + static RxCallback g_rxCallback = nullptr; + + // ------------------------------------------------------------------------- + // Register access helpers + // ------------------------------------------------------------------------- + + static void WriteReg(uint32_t reg, uint32_t value) { + *(volatile uint32_t*)(g_mmioBase + reg) = value; + } + + static uint32_t ReadReg(uint32_t reg) { + return *(volatile uint32_t*)(g_mmioBase + reg); + } + + // ------------------------------------------------------------------------- + // EEPROM access (fallback for MAC address) + // ------------------------------------------------------------------------- + + static uint16_t EepromRead(uint8_t address) { + // Write the address and start bit to EERD + WriteReg(REG_EERD, ((uint32_t)address << 8) | 1); + + // Poll for completion (bit 4 = done) + uint32_t value; + for (int i = 0; i < 10000; i++) { + value = ReadReg(REG_EERD); + if (value & (1 << 4)) { + return (uint16_t)(value >> 16); + } + } + + KernelLogStream(WARNING, "E1000") << "EEPROM read timeout for address " << base::hex << (uint64_t)address; + return 0; + } + + // ------------------------------------------------------------------------- + // MAC address + // ------------------------------------------------------------------------- + + static void ReadMacAddress() { + // Try reading from RAL/RAH first (QEMU usually has it here) + uint32_t ral = ReadReg(REG_RAL); + uint32_t rah = ReadReg(REG_RAH); + + if (ral != 0) { + g_macAddress[0] = (uint8_t)(ral); + g_macAddress[1] = (uint8_t)(ral >> 8); + g_macAddress[2] = (uint8_t)(ral >> 16); + g_macAddress[3] = (uint8_t)(ral >> 24); + g_macAddress[4] = (uint8_t)(rah); + g_macAddress[5] = (uint8_t)(rah >> 8); + } else { + // Fallback: read from EEPROM + uint16_t word0 = EepromRead(0); + uint16_t word1 = EepromRead(1); + uint16_t word2 = EepromRead(2); + + g_macAddress[0] = (uint8_t)(word0); + g_macAddress[1] = (uint8_t)(word0 >> 8); + g_macAddress[2] = (uint8_t)(word1); + g_macAddress[3] = (uint8_t)(word1 >> 8); + g_macAddress[4] = (uint8_t)(word2); + g_macAddress[5] = (uint8_t)(word2 >> 8); + } + + // Write MAC back to RAL/RAH to ensure the filter is set + WriteReg(REG_RAL, + (uint32_t)g_macAddress[0] | + ((uint32_t)g_macAddress[1] << 8) | + ((uint32_t)g_macAddress[2] << 16) | + ((uint32_t)g_macAddress[3] << 24)); + WriteReg(REG_RAH, + (uint32_t)g_macAddress[4] | + ((uint32_t)g_macAddress[5] << 8) | + (1u << 31)); // AV (Address Valid) bit + } + + // ------------------------------------------------------------------------- + // Allocate page-aligned DMA buffer, returns virtual address + // ------------------------------------------------------------------------- + + static uint8_t* AllocateDmaBuffer(uint64_t& outPhysAddr) { + void* virt = Memory::g_pfa->AllocateZeroed(); + outPhysAddr = Memory::SubHHDM(virt); + return (uint8_t*)virt; + } + + // ------------------------------------------------------------------------- + // RX setup + // ------------------------------------------------------------------------- + + static void SetupRx() { + // Allocate RX descriptor ring (needs to be 128-byte aligned, page-aligned is fine) + uint64_t descPhys; + g_rxDescs = (RxDescriptor*)AllocateDmaBuffer(descPhys); + g_rxDescsPhys = descPhys; + + // Allocate packet buffers for each descriptor + for (uint32_t i = 0; i < RX_DESC_COUNT; i++) { + // Each buffer is one page (4096 bytes), sufficient for standard Ethernet frames + g_rxBuffers[i] = AllocateDmaBuffer(g_rxBuffersPhys[i]); + + // For larger buffers (8192), allocate a second page + uint64_t secondPhys; + AllocateDmaBuffer(secondPhys); + + g_rxDescs[i].BufferAddress = g_rxBuffersPhys[i]; + g_rxDescs[i].Status = 0; + g_rxDescs[i].Length = 0; + g_rxDescs[i].Checksum = 0; + g_rxDescs[i].Errors = 0; + g_rxDescs[i].Special = 0; + } + + // Program the descriptor ring base address + WriteReg(REG_RDBAL, (uint32_t)(g_rxDescsPhys & 0xFFFFFFFF)); + WriteReg(REG_RDBAH, (uint32_t)(g_rxDescsPhys >> 32)); + + // Set descriptor ring length (in bytes) + WriteReg(REG_RDLEN, RX_DESC_COUNT * sizeof(RxDescriptor)); + + // Set head and tail pointers + WriteReg(REG_RDH, 0); + WriteReg(REG_RDT, RX_DESC_COUNT - 1); + + g_rxTail = RX_DESC_COUNT - 1; + + // Configure RCTL: enable receiver, accept broadcast, strip CRC, 4096 byte buffers + uint32_t rctl = RCTL_EN | RCTL_BAM | RCTL_SECRC | RCTL_BSIZE_4096 | RCTL_BSEX; + WriteReg(REG_RCTL, rctl); + + KernelLogStream(OK, "E1000") << "RX ring configured: " << base::dec << (uint64_t)RX_DESC_COUNT << " descriptors"; + } + + // ------------------------------------------------------------------------- + // TX setup + // ------------------------------------------------------------------------- + + static void SetupTx() { + // Allocate TX descriptor ring + uint64_t descPhys; + g_txDescs = (TxDescriptor*)AllocateDmaBuffer(descPhys); + g_txDescsPhys = descPhys; + + // Allocate packet buffers for each descriptor + for (uint32_t i = 0; i < TX_DESC_COUNT; i++) { + g_txBuffers[i] = AllocateDmaBuffer(g_txBuffersPhys[i]); + + g_txDescs[i].BufferAddress = g_txBuffersPhys[i]; + g_txDescs[i].Length = 0; + g_txDescs[i].Command = 0; + g_txDescs[i].Status = TXSTA_DD; // Mark as done (available for use) + g_txDescs[i].ChecksumOffset = 0; + g_txDescs[i].ChecksumStart = 0; + g_txDescs[i].Special = 0; + } + + // Program the descriptor ring base address + WriteReg(REG_TDBAL, (uint32_t)(g_txDescsPhys & 0xFFFFFFFF)); + WriteReg(REG_TDBAH, (uint32_t)(g_txDescsPhys >> 32)); + + // Set descriptor ring length (in bytes) + WriteReg(REG_TDLEN, TX_DESC_COUNT * sizeof(TxDescriptor)); + + // Set head and tail pointers + WriteReg(REG_TDH, 0); + WriteReg(REG_TDT, 0); + + g_txTail = 0; + + // Configure TCTL: enable transmitter, pad short packets + // Collision Threshold = 15, Collision Distance = 64 + uint32_t tctl = TCTL_EN | TCTL_PSP + | (15u << TCTL_CT_SHIFT) + | (64u << TCTL_COLD_SHIFT); + WriteReg(REG_TCTL, tctl); + + // Set Inter Packet Gap (recommended values for IEEE 802.3) + // IPGT=10, IPGR1=10, IPGR2=10 + WriteReg(REG_TIPG, 10 | (10 << 10) | (10 << 20)); + + KernelLogStream(OK, "E1000") << "TX ring configured: " << base::dec << (uint64_t)TX_DESC_COUNT << " descriptors"; + } + + // ------------------------------------------------------------------------- + // Interrupt handler + // ------------------------------------------------------------------------- + + static void HandleInterrupt(uint8_t irq) { + (void)irq; + + // Read and clear interrupt cause + uint32_t icr = ReadReg(REG_ICR); + + if (icr & ICR_LSC) { + uint32_t status = ReadReg(REG_STATUS); + bool linkUp = (status & (1 << 1)) != 0; + KernelLogStream(INFO, "E1000") << "Link status change: " << (linkUp ? "UP" : "DOWN"); + } + + if (icr & ICR_RXT0) { + // Process received packets + while (true) { + uint32_t nextIdx = (g_rxTail + 1) % RX_DESC_COUNT; + RxDescriptor& desc = g_rxDescs[nextIdx]; + + if (!(desc.Status & RXSTA_DD)) { + break; // No more packets + } + + uint16_t length = desc.Length; + g_rxPacketCount++; + + // Dispatch to the network stack callback + if (g_rxCallback != nullptr) { + g_rxCallback(g_rxBuffers[nextIdx], length); + } + + // Reset descriptor for reuse + desc.Status = 0; + desc.Length = 0; + desc.Errors = 0; + + g_rxTail = nextIdx; + WriteReg(REG_RDT, g_rxTail); + } + } + + if (icr & (ICR_TXDW | ICR_TXQE)) { + // TX completion - nothing to do for now + } + } + + // ------------------------------------------------------------------------- + // Public API + // ------------------------------------------------------------------------- + + void Initialize() { + KernelLogStream(INFO, "E1000") << "Scanning for Intel E1000 NIC..."; + + // Find the E1000 in the PCI device list + auto& devices = Pci::GetDevices(); + const Pci::PciDevice* e1000Dev = nullptr; + + for (uint64_t i = 0; i < devices.size(); i++) { + if (devices[i].VendorId == VendorIntel && devices[i].DeviceId == DeviceE1000) { + e1000Dev = &devices[i]; + break; + } + } + + if (e1000Dev == nullptr) { + KernelLogStream(WARNING, "E1000") << "No Intel E1000 NIC found"; + return; + } + + KernelLogStream(OK, "E1000") << "Found E1000 at PCI " + << base::hex << (uint64_t)e1000Dev->Bus << ":" + << (uint64_t)e1000Dev->Device << "." << (uint64_t)e1000Dev->Function; + + // Read BAR0 (MMIO base address) + uint32_t bar0 = Pci::LegacyRead32(e1000Dev->Bus, e1000Dev->Device, e1000Dev->Function, PCI_REG_BAR0); + uint64_t mmioPhys = bar0 & 0xFFFFFFF0; // Mask low 4 bits (type/prefetchable flags) + + KernelLogStream(INFO, "E1000") << "BAR0 physical: " << base::hex << mmioPhys; + + // Map the MMIO region (128KB = 32 pages) + constexpr uint64_t MmioSize = 0x20000; // 128KB + for (uint64_t offset = 0; offset < MmioSize; offset += 0x1000) { + Memory::VMM::g_paging->MapMMIO(mmioPhys + offset, Memory::HHDM(mmioPhys + offset)); + } + + g_mmioBase = (volatile uint8_t*)Memory::HHDM(mmioPhys); + + // Enable bus mastering and memory space in PCI command register + uint16_t pciCmd = Pci::LegacyRead16(e1000Dev->Bus, e1000Dev->Device, e1000Dev->Function, PCI_REG_COMMAND); + pciCmd |= PCI_CMD_BUS_MASTER | PCI_CMD_MEM_SPACE; + Pci::LegacyWrite16(e1000Dev->Bus, e1000Dev->Device, e1000Dev->Function, PCI_REG_COMMAND, pciCmd); + + KernelLogStream(OK, "E1000") << "Bus mastering enabled"; + + // Read interrupt line from PCI config + g_irqLine = Pci::LegacyRead8(e1000Dev->Bus, e1000Dev->Device, e1000Dev->Function, PCI_REG_INTERRUPT); + KernelLogStream(INFO, "E1000") << "IRQ line: " << base::dec << (uint64_t)g_irqLine; + + // Reset the device + uint32_t ctrl = ReadReg(REG_CTRL); + WriteReg(REG_CTRL, ctrl | CTRL_RST); + + // Wait for reset to complete (RST bit auto-clears) + for (int i = 0; i < 100000; i++) { + if (!(ReadReg(REG_CTRL) & CTRL_RST)) { + break; + } + } + + // Disable all interrupts during setup + WriteReg(REG_IMC, 0xFFFFFFFF); + + // Set link up + ctrl = ReadReg(REG_CTRL); + ctrl |= CTRL_SLU; + ctrl &= ~(1u << 3); // Clear LRST + ctrl &= ~(1u << 31); // Clear PHY_RST + ctrl &= ~(1u << 7); // Clear ILOS + WriteReg(REG_CTRL, ctrl); + + // Read MAC address + ReadMacAddress(); + + KernelLogStream(OK, "E1000") << "MAC: " + << base::hex + << (uint64_t)g_macAddress[0] << ":" + << (uint64_t)g_macAddress[1] << ":" + << (uint64_t)g_macAddress[2] << ":" + << (uint64_t)g_macAddress[3] << ":" + << (uint64_t)g_macAddress[4] << ":" + << (uint64_t)g_macAddress[5]; + + // Zero out the Multicast Table Array (128 entries) + for (uint32_t i = 0; i < 128; i++) { + WriteReg(REG_MTA + (i * 4), 0); + } + + // Set up RX and TX descriptor rings + SetupRx(); + SetupTx(); + + // Register interrupt handler + Hal::RegisterIrqHandler(g_irqLine, HandleInterrupt); + Hal::IoApic::UnmaskIrq(Hal::IoApic::GetGsiForIrq(g_irqLine)); + + // Enable interrupts: RX, TX, Link Status Change + WriteReg(REG_IMS, ICR_RXT0 | ICR_TXDW | ICR_TXQE | ICR_LSC | ICR_RXDMT0); + + g_initialized = true; + + // Report link status + uint32_t status = ReadReg(REG_STATUS); + bool linkUp = (status & (1 << 1)) != 0; + KernelLogStream(OK, "E1000") << "Initialization complete, link: " << (linkUp ? "UP" : "DOWN"); + } + + bool SendPacket(const uint8_t* data, uint16_t length) { + if (!g_initialized || data == nullptr || length == 0 || length > 1518) { + return false; + } + + // Check if the current TX descriptor is available + TxDescriptor& desc = g_txDescs[g_txTail]; + if (!(desc.Status & TXSTA_DD)) { + KernelLogStream(WARNING, "E1000") << "TX ring full"; + return false; + } + + // Copy packet data into the TX buffer + memcpy(g_txBuffers[g_txTail], data, length); + + // Set up the descriptor + desc.BufferAddress = g_txBuffersPhys[g_txTail]; + desc.Length = length; + desc.Command = TXCMD_EOP | TXCMD_IFCS | TXCMD_RS; + desc.Status = 0; + + // Advance the tail pointer (tells the NIC there's a new packet) + g_txTail = (g_txTail + 1) % TX_DESC_COUNT; + WriteReg(REG_TDT, g_txTail); + + g_txPacketCount++; + return true; + } + + const uint8_t* GetMacAddress() { + return g_macAddress; + } + + bool IsInitialized() { + return g_initialized; + } + + void SetRxCallback(RxCallback callback) { + g_rxCallback = callback; + } + +}; \ No newline at end of file diff --git a/kernel/src/Drivers/Net/E1000.hpp b/kernel/src/Drivers/Net/E1000.hpp new file mode 100644 index 0000000..7aa5157 --- /dev/null +++ b/kernel/src/Drivers/Net/E1000.hpp @@ -0,0 +1,120 @@ +/* + * E1000.hpp + * Intel 82540EM (E1000) Ethernet driver + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include + +namespace Drivers::Net::E1000 { + + // E1000 register offsets (memory-mapped via BAR0) + constexpr uint32_t REG_CTRL = 0x0000; // Device Control + constexpr uint32_t REG_STATUS = 0x0008; // Device Status + constexpr uint32_t REG_EERD = 0x0014; // EEPROM Read + constexpr uint32_t REG_ICR = 0x00C0; // Interrupt Cause Read + constexpr uint32_t REG_IMS = 0x00D0; // Interrupt Mask Set + constexpr uint32_t REG_IMC = 0x00D8; // Interrupt Mask Clear + constexpr uint32_t REG_RCTL = 0x0100; // Receive Control + constexpr uint32_t REG_TCTL = 0x0400; // Transmit Control + constexpr uint32_t REG_TIPG = 0x0410; // Transmit IPG + constexpr uint32_t REG_RDBAL = 0x2800; // RX Descriptor Base Low + constexpr uint32_t REG_RDBAH = 0x2804; // RX Descriptor Base High + constexpr uint32_t REG_RDLEN = 0x2808; // RX Descriptor Length + constexpr uint32_t REG_RDH = 0x2810; // RX Descriptor Head + constexpr uint32_t REG_RDT = 0x2818; // RX Descriptor Tail + constexpr uint32_t REG_TDBAL = 0x3800; // TX Descriptor Base Low + constexpr uint32_t REG_TDBAH = 0x3804; // TX Descriptor Base High + constexpr uint32_t REG_TDLEN = 0x3808; // TX Descriptor Length + constexpr uint32_t REG_TDH = 0x3810; // TX Descriptor Head + constexpr uint32_t REG_TDT = 0x3818; // TX Descriptor Tail + constexpr uint32_t REG_MTA = 0x5200; // Multicast Table Array (128 entries) + constexpr uint32_t REG_RAL = 0x5400; // Receive Address Low + constexpr uint32_t REG_RAH = 0x5404; // Receive Address High + + // CTRL register bits + constexpr uint32_t CTRL_SLU = (1 << 6); // Set Link Up + constexpr uint32_t CTRL_RST = (1 << 26); // Device Reset + + // RCTL register bits + constexpr uint32_t RCTL_EN = (1 << 1); // Receiver Enable + constexpr uint32_t RCTL_SBP = (1 << 2); // Store Bad Packets + constexpr uint32_t RCTL_UPE = (1 << 3); // Unicast Promiscuous + constexpr uint32_t RCTL_MPE = (1 << 4); // Multicast Promiscuous + constexpr uint32_t RCTL_BAM = (1 << 15); // Broadcast Accept Mode + constexpr uint32_t RCTL_BSIZE_4096 = (3 << 16); // Buffer Size 4096 (with BSEX) + constexpr uint32_t RCTL_BSEX = (1 << 25); // Buffer Size Extension + constexpr uint32_t RCTL_SECRC = (1 << 26); // Strip Ethernet CRC + + // TCTL register bits + constexpr uint32_t TCTL_EN = (1 << 1); // Transmit Enable + constexpr uint32_t TCTL_PSP = (1 << 3); // Pad Short Packets + constexpr uint32_t TCTL_CT_SHIFT = 4; // Collision Threshold shift + constexpr uint32_t TCTL_COLD_SHIFT = 12; // Collision Distance shift + + // ICR (interrupt cause) bits + constexpr uint32_t ICR_TXDW = (1 << 0); // TX Descriptor Written Back + constexpr uint32_t ICR_TXQE = (1 << 1); // TX Queue Empty + constexpr uint32_t ICR_LSC = (1 << 2); // Link Status Change + constexpr uint32_t ICR_RXDMT0 = (1 << 4); // RX Descriptor Minimum Threshold + constexpr uint32_t ICR_RXO = (1 << 6); // Receiver Overrun + constexpr uint32_t ICR_RXT0 = (1 << 7); // Receiver Timer Interrupt + + // TX descriptor command bits + constexpr uint8_t TXCMD_EOP = (1 << 0); // End Of Packet + constexpr uint8_t TXCMD_IFCS = (1 << 1); // Insert FCS/CRC + constexpr uint8_t TXCMD_RS = (1 << 3); // Report Status + + // TX descriptor status bits + constexpr uint8_t TXSTA_DD = (1 << 0); // Descriptor Done + + // RX descriptor status bits + constexpr uint8_t RXSTA_DD = (1 << 0); // Descriptor Done + constexpr uint8_t RXSTA_EOP = (1 << 1); // End Of Packet + + // Descriptor ring sizes + constexpr uint32_t RX_DESC_COUNT = 32; + constexpr uint32_t TX_DESC_COUNT = 32; + constexpr uint32_t PACKET_BUFFER_SIZE = 8192; + + // RX descriptor (legacy format, 16 bytes) + struct RxDescriptor { + uint64_t BufferAddress; + uint16_t Length; + uint16_t Checksum; + uint8_t Status; + uint8_t Errors; + uint16_t Special; + } __attribute__((packed)); + + // TX descriptor (legacy format, 16 bytes) + struct TxDescriptor { + uint64_t BufferAddress; + uint16_t Length; + uint8_t ChecksumOffset; + uint8_t Command; + uint8_t Status; + uint8_t ChecksumStart; + uint16_t Special; + } __attribute__((packed)); + + // Initialize the E1000 driver (scans PCI for the device) + void Initialize(); + + // Send a raw Ethernet frame + bool SendPacket(const uint8_t* data, uint16_t length); + + // Get the MAC address (6 bytes) + const uint8_t* GetMacAddress(); + + // Check if the device was found and initialized + bool IsInitialized(); + + // RX callback type: called with (packet data, length) + using RxCallback = void(*)(const uint8_t* data, uint16_t length); + + // Register a callback for received packets + void SetRxCallback(RxCallback callback); + +}; \ No newline at end of file diff --git a/kernel/src/Main.cpp b/kernel/src/Main.cpp index 57d5849..0257a0d 100644 --- a/kernel/src/Main.cpp +++ b/kernel/src/Main.cpp @@ -33,6 +33,8 @@ #include #include #include +#include +#include #include #include @@ -131,6 +133,9 @@ extern "C" void kmain() { Drivers::PS2::Initialize(); Drivers::PS2::Keyboard::Initialize(); Drivers::PS2::Mouse::Initialize(); + + Drivers::Net::E1000::Initialize(); + Net::Initialize(); } #endif diff --git a/kernel/src/Net/Arp.cpp b/kernel/src/Net/Arp.cpp new file mode 100644 index 0000000..415ce8d --- /dev/null +++ b/kernel/src/Net/Arp.cpp @@ -0,0 +1,151 @@ +/* + * Arp.cpp + * Address Resolution Protocol + * Copyright (c) 2025 Daniel Hammer +*/ + +#include "Arp.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Kt; + +namespace Net::Arp { + + // ARP cache entry + struct CacheEntry { + uint32_t Ip; + uint8_t Mac[6]; + uint64_t Timestamp; + bool Valid; + }; + + static constexpr uint32_t ARP_CACHE_SIZE = 32; + static constexpr uint64_t ARP_CACHE_TIMEOUT_MS = 60000; // 60 seconds + + static CacheEntry g_cache[ARP_CACHE_SIZE] = {}; + + void Initialize() { + for (uint32_t i = 0; i < ARP_CACHE_SIZE; i++) { + g_cache[i].Valid = false; + } + KernelLogStream(OK, "Net") << "ARP initialized"; + } + + static void CacheInsert(uint32_t ip, const uint8_t* mac) { + // Look for existing entry or empty slot + uint32_t emptySlot = ARP_CACHE_SIZE; + for (uint32_t i = 0; i < ARP_CACHE_SIZE; i++) { + if (g_cache[i].Valid && g_cache[i].Ip == ip) { + // Update existing entry + memcpy(g_cache[i].Mac, mac, 6); + g_cache[i].Timestamp = Timekeeping::GetMilliseconds(); + return; + } + if (!g_cache[i].Valid && emptySlot == ARP_CACHE_SIZE) { + emptySlot = i; + } + } + + if (emptySlot < ARP_CACHE_SIZE) { + g_cache[emptySlot].Ip = ip; + memcpy(g_cache[emptySlot].Mac, mac, 6); + g_cache[emptySlot].Timestamp = Timekeeping::GetMilliseconds(); + g_cache[emptySlot].Valid = true; + } + } + + static bool CacheLookup(uint32_t ip, uint8_t* outMac) { + uint64_t now = Timekeeping::GetMilliseconds(); + for (uint32_t i = 0; i < ARP_CACHE_SIZE; i++) { + if (g_cache[i].Valid && g_cache[i].Ip == ip) { + if ((now - g_cache[i].Timestamp) > ARP_CACHE_TIMEOUT_MS) { + g_cache[i].Valid = false; + return false; + } + memcpy(outMac, g_cache[i].Mac, 6); + return true; + } + } + return false; + } + + void OnPacketReceived(const uint8_t* data, uint16_t length) { + if (length < sizeof(Packet)) { + return; + } + + const Packet* pkt = (const Packet*)data; + + if (Ntohs(pkt->HardwareType) != HW_TYPE_ETHERNET || + Ntohs(pkt->ProtocolType) != PROTO_TYPE_IPV4) { + return; + } + + uint32_t senderIp = pkt->SenderIp; // Already in network byte order in struct + uint32_t targetIp = pkt->TargetIp; + + // Cache the sender's IP->MAC mapping + CacheInsert(senderIp, pkt->SenderMac); + + uint16_t op = Ntohs(pkt->Operation); + + if (op == OP_REQUEST && targetIp == GetIpAddress()) { + // Someone is asking for our MAC address -- send a reply + Packet reply; + reply.HardwareType = Htons(HW_TYPE_ETHERNET); + reply.ProtocolType = Htons(PROTO_TYPE_IPV4); + reply.HardwareAddrLen = 6; + reply.ProtocolAddrLen = 4; + reply.Operation = Htons(OP_REPLY); + + memcpy(reply.SenderMac, Drivers::Net::E1000::GetMacAddress(), 6); + reply.SenderIp = GetIpAddress(); + memcpy(reply.TargetMac, pkt->SenderMac, 6); + reply.TargetIp = senderIp; + + Ethernet::Send(pkt->SenderMac, Ethernet::ETHERTYPE_ARP, + (const uint8_t*)&reply, sizeof(Packet)); + } + } + + bool Resolve(uint32_t ip, uint8_t* outMac) { + // Broadcast address + if (ip == 0xFFFFFFFF) { + memcpy(outMac, Ethernet::BROADCAST_MAC, 6); + return true; + } + + if (CacheLookup(ip, outMac)) { + return true; + } + + // Not in cache, send a request + SendRequest(ip); + return false; + } + + void SendRequest(uint32_t targetIp) { + Packet req; + req.HardwareType = Htons(HW_TYPE_ETHERNET); + req.ProtocolType = Htons(PROTO_TYPE_IPV4); + req.HardwareAddrLen = 6; + req.ProtocolAddrLen = 4; + req.Operation = Htons(OP_REQUEST); + + memcpy(req.SenderMac, Drivers::Net::E1000::GetMacAddress(), 6); + req.SenderIp = GetIpAddress(); + memset(req.TargetMac, 0, 6); + req.TargetIp = targetIp; + + Ethernet::Send(Ethernet::BROADCAST_MAC, Ethernet::ETHERTYPE_ARP, + (const uint8_t*)&req, sizeof(Packet)); + } + +} diff --git a/kernel/src/Net/Arp.hpp b/kernel/src/Net/Arp.hpp new file mode 100644 index 0000000..57310ed --- /dev/null +++ b/kernel/src/Net/Arp.hpp @@ -0,0 +1,43 @@ +/* + * Arp.hpp + * Address Resolution Protocol + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include + +namespace Net::Arp { + + constexpr uint16_t HW_TYPE_ETHERNET = 1; + constexpr uint16_t PROTO_TYPE_IPV4 = 0x0800; + + constexpr uint16_t OP_REQUEST = 1; + constexpr uint16_t OP_REPLY = 2; + + struct Packet { + uint16_t HardwareType; + uint16_t ProtocolType; + uint8_t HardwareAddrLen; + uint8_t ProtocolAddrLen; + uint16_t Operation; + uint8_t SenderMac[6]; + uint32_t SenderIp; + uint8_t TargetMac[6]; + uint32_t TargetIp; + } __attribute__((packed)); + + // Initialize the ARP subsystem + void Initialize(); + + // Handle an incoming ARP packet (called by Ethernet layer) + void OnPacketReceived(const uint8_t* data, uint16_t length); + + // Resolve an IP address to a MAC address. Returns true if found in cache. + // If not cached, sends an ARP request and returns false. + bool Resolve(uint32_t ip, uint8_t* outMac); + + // Send an ARP request for the given IP + void SendRequest(uint32_t targetIp); + +} diff --git a/kernel/src/Net/ByteOrder.hpp b/kernel/src/Net/ByteOrder.hpp new file mode 100644 index 0000000..a0d3d73 --- /dev/null +++ b/kernel/src/Net/ByteOrder.hpp @@ -0,0 +1,31 @@ +/* + * ByteOrder.hpp + * Network byte order conversion utilities + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include + +namespace Net { + + inline uint16_t Htons(uint16_t host) { + return (uint16_t)((host >> 8) | (host << 8)); + } + + inline uint16_t Ntohs(uint16_t net) { + return Htons(net); + } + + inline uint32_t Htonl(uint32_t host) { + return ((host >> 24) & 0x000000FF) + | ((host >> 8) & 0x0000FF00) + | ((host << 8) & 0x00FF0000) + | ((host << 24) & 0xFF000000); + } + + inline uint32_t Ntohl(uint32_t net) { + return Htonl(net); + } + +} diff --git a/kernel/src/Net/Ethernet.cpp b/kernel/src/Net/Ethernet.cpp new file mode 100644 index 0000000..0761e91 --- /dev/null +++ b/kernel/src/Net/Ethernet.cpp @@ -0,0 +1,65 @@ +/* + * Ethernet.cpp + * Ethernet frame layer + * Copyright (c) 2025 Daniel Hammer +*/ + +#include "Ethernet.hpp" +#include +#include +#include +#include +#include +#include +#include + +using namespace Kt; + +namespace Net::Ethernet { + + void Initialize() { + KernelLogStream(OK, "Net") << "Ethernet layer initialized"; + } + + bool Send(const uint8_t* destMac, uint16_t etherType, const uint8_t* payload, uint16_t payloadLen) { + if (payload == nullptr || payloadLen == 0 || payloadLen > MAX_PAYLOAD_SIZE) { + return false; + } + + uint8_t frame[MAX_FRAME_SIZE]; + Header* hdr = (Header*)frame; + + memcpy(hdr->DestMac, destMac, 6); + memcpy(hdr->SrcMac, Drivers::Net::E1000::GetMacAddress(), 6); + hdr->EtherType = Htons(etherType); + + memcpy(frame + HEADER_SIZE, payload, payloadLen); + + uint16_t totalLen = HEADER_SIZE + payloadLen; + + return Drivers::Net::E1000::SendPacket(frame, totalLen); + } + + void OnFrameReceived(const uint8_t* data, uint16_t length) { + if (length < HEADER_SIZE) { + return; + } + + const Header* hdr = (const Header*)data; + uint16_t etherType = Ntohs(hdr->EtherType); + const uint8_t* payload = data + HEADER_SIZE; + uint16_t payloadLen = length - HEADER_SIZE; + + switch (etherType) { + case ETHERTYPE_ARP: + Arp::OnPacketReceived(payload, payloadLen); + break; + case ETHERTYPE_IPV4: + Ipv4::OnPacketReceived(payload, payloadLen); + break; + default: + break; + } + } + +} diff --git a/kernel/src/Net/Ethernet.hpp b/kernel/src/Net/Ethernet.hpp new file mode 100644 index 0000000..5a80404 --- /dev/null +++ b/kernel/src/Net/Ethernet.hpp @@ -0,0 +1,36 @@ +/* + * Ethernet.hpp + * Ethernet frame layer + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include + +namespace Net::Ethernet { + + constexpr uint16_t ETHERTYPE_IPV4 = 0x0800; + constexpr uint16_t ETHERTYPE_ARP = 0x0806; + + constexpr uint8_t BROADCAST_MAC[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + + constexpr uint16_t HEADER_SIZE = 14; + constexpr uint16_t MAX_FRAME_SIZE = 1518; + constexpr uint16_t MAX_PAYLOAD_SIZE = MAX_FRAME_SIZE - HEADER_SIZE; + + struct Header { + uint8_t DestMac[6]; + uint8_t SrcMac[6]; + uint16_t EtherType; + } __attribute__((packed)); + + // Initialize the Ethernet layer (hooks into E1000 RX path) + void Initialize(); + + // Send an Ethernet frame with the given EtherType and payload + bool Send(const uint8_t* destMac, uint16_t etherType, const uint8_t* payload, uint16_t payloadLen); + + // Called by E1000 RX handler to dispatch received frames + void OnFrameReceived(const uint8_t* data, uint16_t length); + +} diff --git a/kernel/src/Net/Icmp.cpp b/kernel/src/Net/Icmp.cpp new file mode 100644 index 0000000..d5c7a12 --- /dev/null +++ b/kernel/src/Net/Icmp.cpp @@ -0,0 +1,60 @@ +/* + * Icmp.cpp + * Internet Control Message Protocol + * Copyright (c) 2025 Daniel Hammer +*/ + +#include "Icmp.hpp" +#include +#include +#include +#include +#include + +using namespace Kt; + +namespace Net::Icmp { + + void Initialize() { + KernelLogStream(OK, "Net") << "ICMP initialized"; + } + + void OnPacketReceived(uint32_t srcIp, const uint8_t* data, uint16_t length) { + if (length < sizeof(Header)) { + return; + } + + const Header* hdr = (const Header*)data; + + // Verify checksum + if (Ipv4::Checksum(data, length) != 0) { + return; + } + + if (hdr->Type == TYPE_ECHO_REQUEST && hdr->Code == 0) { + KernelLogStream(INFO, "Net") << "ICMP echo request from " + << base::dec + << (uint64_t)(srcIp & 0xFF) << "." + << (uint64_t)((srcIp >> 8) & 0xFF) << "." + << (uint64_t)((srcIp >> 16) & 0xFF) << "." + << (uint64_t)((srcIp >> 24) & 0xFF); + + // Build echo reply -- same payload, different type + uint8_t reply[1500]; + if (length > sizeof(reply)) { + return; + } + + memcpy(reply, data, length); + + Header* replyHdr = (Header*)reply; + replyHdr->Type = TYPE_ECHO_REPLY; + replyHdr->Code = 0; + replyHdr->Checksum = 0; + replyHdr->Checksum = Ipv4::Checksum(reply, length); + + Ipv4::Send(srcIp, Ipv4::PROTO_ICMP, reply, length); + } + } + +} diff --git a/kernel/src/Net/Icmp.hpp b/kernel/src/Net/Icmp.hpp new file mode 100644 index 0000000..12d9841 --- /dev/null +++ b/kernel/src/Net/Icmp.hpp @@ -0,0 +1,29 @@ +/* + * Icmp.hpp + * Internet Control Message Protocol + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include + +namespace Net::Icmp { + + constexpr uint8_t TYPE_ECHO_REPLY = 0; + constexpr uint8_t TYPE_ECHO_REQUEST = 8; + + struct Header { + uint8_t Type; + uint8_t Code; + uint16_t Checksum; + uint16_t Identifier; + uint16_t Sequence; + } __attribute__((packed)); + + // Initialize the ICMP subsystem + void Initialize(); + + // Handle an incoming ICMP packet (called by IPv4 layer) + void OnPacketReceived(uint32_t srcIp, const uint8_t* data, uint16_t length); + +} diff --git a/kernel/src/Net/Ipv4.cpp b/kernel/src/Net/Ipv4.cpp new file mode 100644 index 0000000..07981af --- /dev/null +++ b/kernel/src/Net/Ipv4.cpp @@ -0,0 +1,188 @@ +/* + * Ipv4.cpp + * Internet Protocol version 4 + * Copyright (c) 2025 Daniel Hammer +*/ + +#include "Ipv4.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Kt; + +namespace Net::Ipv4 { + + static uint16_t g_identification = 0; + + void Initialize() { + g_identification = 0; + KernelLogStream(OK, "Net") << "IPv4 initialized, IP: " + << base::dec + << (uint64_t)(GetIpAddress() & 0xFF) << "." + << (uint64_t)((GetIpAddress() >> 8) & 0xFF) << "." + << (uint64_t)((GetIpAddress() >> 16) & 0xFF) << "." + << (uint64_t)((GetIpAddress() >> 24) & 0xFF); + } + + uint16_t Checksum(const void* data, uint16_t length) { + const uint16_t* ptr = (const uint16_t*)data; + uint32_t sum = 0; + + while (length > 1) { + sum += *ptr++; + length -= 2; + } + + // Handle odd byte + if (length == 1) { + sum += *(const uint8_t*)ptr; + } + + // Fold 32-bit sum into 16 bits + while (sum >> 16) { + sum = (sum & 0xFFFF) + (sum >> 16); + } + + return (uint16_t)(~sum); + } + + uint16_t PseudoHeaderChecksum(uint32_t srcIp, uint32_t dstIp, uint8_t protocol, + uint16_t length, const void* data, uint16_t dataLen) { + uint32_t sum = 0; + + // Pseudo-header fields (already in network byte order) + sum += (srcIp & 0xFFFF); + sum += (srcIp >> 16); + sum += (dstIp & 0xFFFF); + sum += (dstIp >> 16); + sum += Htons(protocol); + sum += Htons(length); + + // Data + const uint16_t* ptr = (const uint16_t*)data; + uint16_t remaining = dataLen; + while (remaining > 1) { + sum += *ptr++; + remaining -= 2; + } + if (remaining == 1) { + sum += *(const uint8_t*)ptr; + } + + while (sum >> 16) { + sum = (sum & 0xFFFF) + (sum >> 16); + } + + return (uint16_t)(~sum); + } + + void OnPacketReceived(const uint8_t* data, uint16_t length) { + if (length < HEADER_SIZE) { + return; + } + + const Header* hdr = (const Header*)data; + + // Verify version + uint8_t version = (hdr->VersionIhl >> 4) & 0xF; + if (version != 4) { + return; + } + + // Get header length + uint8_t ihl = (hdr->VersionIhl & 0xF) * 4; + if (ihl < HEADER_SIZE || ihl > length) { + return; + } + + // Verify checksum + if (Checksum(data, ihl) != 0) { + return; + } + + uint16_t totalLen = Ntohs(hdr->TotalLength); + if (totalLen > length) { + return; + } + + // Check destination: accept packets addressed to us or broadcast + uint32_t ourIp = GetIpAddress(); + if (hdr->DstIp != ourIp && hdr->DstIp != 0xFFFFFFFF) { + return; + } + + const uint8_t* payload = data + ihl; + uint16_t payloadLen = totalLen - ihl; + + switch (hdr->Protocol) { + case PROTO_ICMP: + Icmp::OnPacketReceived(hdr->SrcIp, payload, payloadLen); + break; + case PROTO_UDP: + Udp::OnPacketReceived(hdr->SrcIp, hdr->DstIp, payload, payloadLen); + break; + case PROTO_TCP: + Tcp::OnPacketReceived(hdr->SrcIp, hdr->DstIp, payload, payloadLen); + break; + default: + break; + } + } + + bool Send(uint32_t destIp, uint8_t protocol, const uint8_t* payload, uint16_t payloadLen) { + if (payloadLen > (Ethernet::MAX_PAYLOAD_SIZE - HEADER_SIZE)) { + return false; + } + + // Determine next-hop IP and resolve MAC + uint32_t nextHop = GetNextHop(destIp); + uint8_t destMac[6]; + + if (!Arp::Resolve(nextHop, destMac)) { + // ARP request sent, wait briefly and retry + for (int attempt = 0; attempt < 3; attempt++) { + Timekeeping::Sleep(50); + if (Arp::Resolve(nextHop, destMac)) { + break; + } + } + // Final check + if (!Arp::Resolve(nextHop, destMac)) { + return false; + } + } + + // Build IP packet + uint8_t packet[Ethernet::MAX_PAYLOAD_SIZE]; + Header* hdr = (Header*)packet; + + hdr->VersionIhl = (4 << 4) | 5; // IPv4, 5 dwords (20 bytes) + hdr->Tos = 0; + hdr->TotalLength = Htons(HEADER_SIZE + payloadLen); + hdr->Identification = Htons(g_identification++); + hdr->FlagsFragment = 0; + hdr->Ttl = DEFAULT_TTL; + hdr->Protocol = protocol; + hdr->Checksum = 0; + hdr->SrcIp = GetIpAddress(); + hdr->DstIp = destIp; + + // Calculate header checksum + hdr->Checksum = Checksum(hdr, HEADER_SIZE); + + // Copy payload + memcpy(packet + HEADER_SIZE, payload, payloadLen); + + return Ethernet::Send(destMac, Ethernet::ETHERTYPE_IPV4, packet, HEADER_SIZE + payloadLen); + } + +} diff --git a/kernel/src/Net/Ipv4.hpp b/kernel/src/Net/Ipv4.hpp new file mode 100644 index 0000000..f77fe41 --- /dev/null +++ b/kernel/src/Net/Ipv4.hpp @@ -0,0 +1,49 @@ +/* + * Ipv4.hpp + * Internet Protocol version 4 + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include + +namespace Net::Ipv4 { + + constexpr uint8_t PROTO_ICMP = 1; + constexpr uint8_t PROTO_TCP = 6; + constexpr uint8_t PROTO_UDP = 17; + + constexpr uint8_t DEFAULT_TTL = 64; + + constexpr uint16_t HEADER_SIZE = 20; // Without options + + struct Header { + uint8_t VersionIhl; // Version (4 bits) + IHL (4 bits) + uint8_t Tos; // Type of Service + uint16_t TotalLength; + uint16_t Identification; + uint16_t FlagsFragment; // Flags (3 bits) + Fragment Offset (13 bits) + uint8_t Ttl; + uint8_t Protocol; + uint16_t Checksum; + uint32_t SrcIp; + uint32_t DstIp; + } __attribute__((packed)); + + // Initialize the IPv4 subsystem + void Initialize(); + + // Handle an incoming IP packet (called by Ethernet layer) + void OnPacketReceived(const uint8_t* data, uint16_t length); + + // Send an IP packet with the given protocol and payload + bool Send(uint32_t destIp, uint8_t protocol, const uint8_t* payload, uint16_t payloadLen); + + // Compute the Internet checksum over a buffer + uint16_t Checksum(const void* data, uint16_t length); + + // Compute TCP/UDP pseudo-header checksum + uint16_t PseudoHeaderChecksum(uint32_t srcIp, uint32_t dstIp, uint8_t protocol, + uint16_t length, const void* data, uint16_t dataLen); + +} diff --git a/kernel/src/Net/Net.cpp b/kernel/src/Net/Net.cpp new file mode 100644 index 0000000..ba16d03 --- /dev/null +++ b/kernel/src/Net/Net.cpp @@ -0,0 +1,46 @@ +/* + * Net.cpp + * Network stack initialization + * Copyright (c) 2025 Daniel Hammer +*/ + +#include "Net.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Kt; + +namespace Net { + + void Initialize() { + if (!Drivers::Net::E1000::IsInitialized()) { + KernelLogStream(WARNING, "Net") << "E1000 not initialized, skipping network stack"; + return; + } + + // Initialize layers bottom-up + Ethernet::Initialize(); + Arp::Initialize(); + Ipv4::Initialize(); + Icmp::Initialize(); + Udp::Initialize(); + Tcp::Initialize(); + + // Hook E1000 RX to our Ethernet dispatcher + Drivers::Net::E1000::SetRxCallback(Ethernet::OnFrameReceived); + + // Send a gratuitous ARP to announce ourselves on the network + Arp::SendRequest(GetIpAddress()); + + KernelLogStream(OK, "Net") << "Network stack initialized"; + } + +} diff --git a/kernel/src/Net/Net.hpp b/kernel/src/Net/Net.hpp new file mode 100644 index 0000000..1786ace --- /dev/null +++ b/kernel/src/Net/Net.hpp @@ -0,0 +1,14 @@ +/* + * Net.hpp + * Network stack initialization + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once + +namespace Net { + + // Initialize the entire networking stack + void Initialize(); + +} diff --git a/kernel/src/Net/NetConfig.cpp b/kernel/src/Net/NetConfig.cpp new file mode 100644 index 0000000..ae965ad --- /dev/null +++ b/kernel/src/Net/NetConfig.cpp @@ -0,0 +1,36 @@ +/* + * NetConfig.cpp + * Network configuration (static IP, gateway, etc.) + * Copyright (c) 2025 Daniel Hammer +*/ + +#include "NetConfig.hpp" + +namespace Net { + + // QEMU user-mode networking defaults + static uint32_t g_ipAddress = Ipv4Addr(10, 0, 68, 99); + static uint32_t g_subnetMask = Ipv4Addr(255, 255, 255, 0); + static uint32_t g_gateway = Ipv4Addr(10, 0, 68, 1); + + uint32_t GetIpAddress() { return g_ipAddress; } + void SetIpAddress(uint32_t ip) { g_ipAddress = ip; } + + uint32_t GetSubnetMask() { return g_subnetMask; } + void SetSubnetMask(uint32_t mask) { g_subnetMask = mask; } + + uint32_t GetGateway() { return g_gateway; } + void SetGateway(uint32_t gw) { g_gateway = gw; } + + bool IsLocalSubnet(uint32_t destIp) { + return (destIp & g_subnetMask) == (g_ipAddress & g_subnetMask); + } + + uint32_t GetNextHop(uint32_t destIp) { + if (IsLocalSubnet(destIp)) { + return destIp; + } + return g_gateway; + } + +} diff --git a/kernel/src/Net/NetConfig.hpp b/kernel/src/Net/NetConfig.hpp new file mode 100644 index 0000000..e4e1f97 --- /dev/null +++ b/kernel/src/Net/NetConfig.hpp @@ -0,0 +1,37 @@ +/* + * NetConfig.hpp + * Network configuration (static IP, gateway, etc.) + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include + +namespace Net { + + // Pack an IPv4 address from four octets (in host visual order: a.b.c.d) + // Returns in network byte order + inline uint32_t Ipv4Addr(uint8_t a, uint8_t b, uint8_t c, uint8_t d) { + return (uint32_t)a | ((uint32_t)b << 8) | ((uint32_t)c << 16) | ((uint32_t)d << 24); + } + + // Get/set the local IP address (network byte order) + uint32_t GetIpAddress(); + void SetIpAddress(uint32_t ip); + + // Get/set the subnet mask (network byte order) + uint32_t GetSubnetMask(); + void SetSubnetMask(uint32_t mask); + + // Get/set the default gateway (network byte order) + uint32_t GetGateway(); + void SetGateway(uint32_t gw); + + // Check if a destination IP is on the local subnet + bool IsLocalSubnet(uint32_t destIp); + + // Get the next-hop IP for a given destination + // Returns destIp if local, or gateway if remote + uint32_t GetNextHop(uint32_t destIp); + +} diff --git a/kernel/src/Net/Tcp.cpp b/kernel/src/Net/Tcp.cpp new file mode 100644 index 0000000..85527e9 --- /dev/null +++ b/kernel/src/Net/Tcp.cpp @@ -0,0 +1,686 @@ +/* + * Tcp.cpp + * Transmission Control Protocol + * Copyright (c) 2025 Daniel Hammer +*/ + +#include "Tcp.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Kt; + +namespace Net::Tcp { + + // Receive buffer size per connection + static constexpr uint16_t RECV_BUFFER_SIZE = 4096; + static constexpr uint16_t WINDOW_SIZE = 4096; + static constexpr uint32_t MAX_CONNECTIONS = 16; + static constexpr uint64_t RETRANSMIT_TIMEOUT_MS = 1000; + static constexpr int MAX_RETRANSMITS = 5; + static constexpr uint64_t TIME_WAIT_MS = 2000; + + struct Connection { + State CurrentState; + uint32_t LocalIp; + uint16_t LocalPort; + uint32_t RemoteIp; + uint16_t RemotePort; + + // Sequence numbers + uint32_t SendNext; // Next sequence number to send + uint32_t SendUnack; // Oldest unacknowledged sequence number + uint32_t RecvNext; // Next expected sequence number from remote + + // Receive buffer (ring buffer) + uint8_t RecvBuffer[RECV_BUFFER_SIZE]; + uint16_t RecvHead; // Read position + uint16_t RecvTail; // Write position + uint16_t RecvCount; // Bytes in buffer + + // Retransmission tracking + uint8_t RetransmitBuffer[1500]; + uint16_t RetransmitLen; + uint64_t RetransmitTime; + int RetransmitCount; + + // For Listen/Accept + bool PendingAccept; + uint32_t PendingRemoteIp; + uint16_t PendingRemotePort; + uint32_t PendingSeq; + + bool Active; + + kcp::Spinlock Lock; + }; + + static Connection g_connections[MAX_CONNECTIONS] = {}; + static kcp::Spinlock g_connectionsLock; + + // Simple ISN generator using timer + static uint32_t GenerateISN() { + return (uint32_t)(Timekeeping::GetMilliseconds() * 2654435761u); + } + + static Connection* FindConnection(uint32_t remoteIp, uint16_t remotePort, + uint16_t localPort) { + for (uint32_t i = 0; i < MAX_CONNECTIONS; i++) { + Connection* c = &g_connections[i]; + if (c->Active && + c->LocalPort == localPort && + c->RemoteIp == remoteIp && + c->RemotePort == remotePort && + c->CurrentState != State::Listen) { + return c; + } + } + return nullptr; + } + + static Connection* FindListener(uint16_t localPort) { + for (uint32_t i = 0; i < MAX_CONNECTIONS; i++) { + Connection* c = &g_connections[i]; + if (c->Active && c->LocalPort == localPort && c->CurrentState == State::Listen) { + return c; + } + } + return nullptr; + } + + static Connection* AllocateConnection() { + for (uint32_t i = 0; i < MAX_CONNECTIONS; i++) { + if (!g_connections[i].Active) { + Connection* c = &g_connections[i]; + memset(c, 0, sizeof(Connection)); + c->Active = true; + c->CurrentState = State::Closed; + return c; + } + } + return nullptr; + } + + static bool SendSegment(Connection* conn, uint8_t flags, + const uint8_t* payload, uint16_t payloadLen) { + uint8_t packet[1500]; + Header* hdr = (Header*)packet; + + hdr->SrcPort = Htons(conn->LocalPort); + hdr->DstPort = Htons(conn->RemotePort); + hdr->SeqNum = Htonl(conn->SendNext); + hdr->AckNum = Htonl(conn->RecvNext); + hdr->DataOffset = (HEADER_SIZE / 4) << 4; + hdr->Flags = flags; + hdr->Window = Htons(WINDOW_SIZE); + hdr->Checksum = 0; + hdr->UrgentPtr = 0; + + uint16_t totalLen = HEADER_SIZE + payloadLen; + if (payload != nullptr && payloadLen > 0) { + memcpy(packet + HEADER_SIZE, payload, payloadLen); + } + + // Calculate checksum with pseudo-header + hdr->Checksum = Ipv4::PseudoHeaderChecksum( + conn->LocalIp, conn->RemoteIp, Ipv4::PROTO_TCP, + totalLen, packet, totalLen); + + return Ipv4::Send(conn->RemoteIp, Ipv4::PROTO_TCP, packet, totalLen); + } + + // Send a RST to an unexpected packet + static void SendReset(uint32_t destIp, uint16_t destPort, uint16_t srcPort, + uint32_t seqNum, uint32_t ackNum) { + uint8_t packet[HEADER_SIZE]; + Header* hdr = (Header*)packet; + + hdr->SrcPort = Htons(srcPort); + hdr->DstPort = Htons(destPort); + hdr->SeqNum = Htonl(seqNum); + hdr->AckNum = Htonl(ackNum); + hdr->DataOffset = (HEADER_SIZE / 4) << 4; + hdr->Flags = FLAG_RST | FLAG_ACK; + hdr->Window = 0; + hdr->Checksum = 0; + hdr->UrgentPtr = 0; + + uint32_t localIp = Net::GetIpAddress(); + hdr->Checksum = Ipv4::PseudoHeaderChecksum( + localIp, destIp, Ipv4::PROTO_TCP, HEADER_SIZE, packet, HEADER_SIZE); + + Ipv4::Send(destIp, Ipv4::PROTO_TCP, packet, HEADER_SIZE); + } + + static void RecvBufferWrite(Connection* conn, const uint8_t* data, uint16_t len) { + for (uint16_t i = 0; i < len && conn->RecvCount < RECV_BUFFER_SIZE; i++) { + conn->RecvBuffer[conn->RecvTail] = data[i]; + conn->RecvTail = (conn->RecvTail + 1) % RECV_BUFFER_SIZE; + conn->RecvCount++; + } + } + + void Initialize() { + for (uint32_t i = 0; i < MAX_CONNECTIONS; i++) { + g_connections[i].Active = false; + } + KernelLogStream(OK, "Net") << "TCP initialized"; + } + + void OnPacketReceived(uint32_t srcIp, uint32_t dstIp, const uint8_t* data, uint16_t length) { + if (length < HEADER_SIZE) { + return; + } + + const Header* hdr = (const Header*)data; + + // Verify checksum + uint16_t check = Ipv4::PseudoHeaderChecksum(srcIp, dstIp, Ipv4::PROTO_TCP, + length, data, length); + if (check != 0) { + return; + } + + uint16_t srcPort = Ntohs(hdr->SrcPort); + uint16_t dstPort = Ntohs(hdr->DstPort); + uint32_t seqNum = Ntohl(hdr->SeqNum); + uint32_t ackNum = Ntohl(hdr->AckNum); + uint8_t flags = hdr->Flags; + uint8_t dataOff = (hdr->DataOffset >> 4) * 4; + + if (dataOff < HEADER_SIZE || dataOff > length) { + return; + } + + const uint8_t* payload = data + dataOff; + uint16_t payloadLen = length - dataOff; + + // Find existing connection + Connection* conn = FindConnection(srcIp, srcPort, dstPort); + + if (conn == nullptr) { + // Check for a listening socket + if (flags & FLAG_SYN) { + Connection* listener = FindListener(dstPort); + if (listener != nullptr) { + // Signal the listener about this incoming connection + listener->Lock.Acquire(); + listener->PendingAccept = true; + listener->PendingRemoteIp = srcIp; + listener->PendingRemotePort = srcPort; + listener->PendingSeq = seqNum; + listener->Lock.Release(); + return; + } + } + + // No matching connection or listener -- send RST + if (!(flags & FLAG_RST)) { + if (flags & FLAG_ACK) { + SendReset(srcIp, srcPort, dstPort, ackNum, 0); + } else { + uint32_t rstAck = seqNum + payloadLen; + if (flags & FLAG_SYN) rstAck++; + if (flags & FLAG_FIN) rstAck++; + SendReset(srcIp, srcPort, dstPort, 0, rstAck); + } + } + return; + } + + conn->Lock.Acquire(); + + // RST handling + if (flags & FLAG_RST) { + conn->CurrentState = State::Closed; + conn->Active = false; + conn->Lock.Release(); + return; + } + + switch (conn->CurrentState) { + case State::SynSent: { + // Expecting SYN-ACK + if ((flags & (FLAG_SYN | FLAG_ACK)) == (FLAG_SYN | FLAG_ACK)) { + if (ackNum == conn->SendNext) { + conn->RecvNext = seqNum + 1; + conn->SendUnack = ackNum; + conn->CurrentState = State::Established; + + // Send ACK + SendSegment(conn, FLAG_ACK, nullptr, 0); + + KernelLogStream(INFO, "Net") << "TCP connection established to port " + << base::dec << (uint64_t)conn->RemotePort; + } + } + break; + } + + case State::SynReceived: { + // Expecting ACK to complete handshake + if (flags & FLAG_ACK) { + if (ackNum == conn->SendNext) { + conn->SendUnack = ackNum; + conn->CurrentState = State::Established; + } + } + break; + } + + case State::Established: { + // Handle incoming data + if (flags & FLAG_ACK) { + conn->SendUnack = ackNum; + } + + if (payloadLen > 0 && seqNum == conn->RecvNext) { + RecvBufferWrite(conn, payload, payloadLen); + conn->RecvNext += payloadLen; + + // Send ACK + SendSegment(conn, FLAG_ACK, nullptr, 0); + } + + if (flags & FLAG_FIN) { + conn->RecvNext = seqNum + payloadLen + 1; + conn->CurrentState = State::CloseWait; + + // Send ACK for the FIN + SendSegment(conn, FLAG_ACK, nullptr, 0); + } + break; + } + + case State::FinWait1: { + if (flags & FLAG_ACK) { + conn->SendUnack = ackNum; + if (flags & FLAG_FIN) { + conn->RecvNext = seqNum + 1; + conn->CurrentState = State::TimeWait; + SendSegment(conn, FLAG_ACK, nullptr, 0); + } else { + conn->CurrentState = State::FinWait2; + } + } else if (flags & FLAG_FIN) { + conn->RecvNext = seqNum + 1; + conn->CurrentState = State::TimeWait; + SendSegment(conn, FLAG_ACK, nullptr, 0); + } + break; + } + + case State::FinWait2: { + if (flags & FLAG_FIN) { + conn->RecvNext = seqNum + 1; + conn->CurrentState = State::TimeWait; + SendSegment(conn, FLAG_ACK, nullptr, 0); + } + break; + } + + case State::LastAck: { + if (flags & FLAG_ACK) { + conn->CurrentState = State::Closed; + conn->Active = false; + } + break; + } + + case State::TimeWait: { + // Ignore, will time out + break; + } + + default: + break; + } + + conn->Lock.Release(); + } + + Connection* Listen(uint16_t port) { + g_connectionsLock.Acquire(); + Connection* conn = AllocateConnection(); + g_connectionsLock.Release(); + + if (conn == nullptr) { + return nullptr; + } + + conn->LocalIp = Net::GetIpAddress(); + conn->LocalPort = port; + conn->CurrentState = State::Listen; + conn->PendingAccept = false; + + KernelLogStream(INFO, "Net") << "TCP listening on port " << base::dec << (uint64_t)port; + return conn; + } + + Connection* Accept(Connection* listener) { + if (listener == nullptr || listener->CurrentState != State::Listen) { + return nullptr; + } + + // Block until a SYN arrives + while (true) { + listener->Lock.Acquire(); + if (listener->PendingAccept) { + listener->PendingAccept = false; + + uint32_t remoteIp = listener->PendingRemoteIp; + uint16_t remotePort = listener->PendingRemotePort; + uint32_t remoteSeq = listener->PendingSeq; + listener->Lock.Release(); + + // Allocate a new connection for this client + g_connectionsLock.Acquire(); + Connection* conn = AllocateConnection(); + g_connectionsLock.Release(); + + if (conn == nullptr) { + return nullptr; + } + + conn->LocalIp = Net::GetIpAddress(); + conn->LocalPort = listener->LocalPort; + conn->RemoteIp = remoteIp; + conn->RemotePort = remotePort; + conn->RecvNext = remoteSeq + 1; + + uint32_t isn = GenerateISN(); + conn->SendNext = isn; + conn->SendUnack = isn; + conn->CurrentState = State::SynReceived; + + // Send SYN-ACK + conn->SendNext = isn + 1; + { + // Manually build the SYN-ACK with ISN as seqnum + uint8_t packet[HEADER_SIZE]; + Header* hdr = (Header*)packet; + + hdr->SrcPort = Htons(conn->LocalPort); + hdr->DstPort = Htons(conn->RemotePort); + hdr->SeqNum = Htonl(isn); + hdr->AckNum = Htonl(conn->RecvNext); + hdr->DataOffset = (HEADER_SIZE / 4) << 4; + hdr->Flags = FLAG_SYN | FLAG_ACK; + hdr->Window = Htons(WINDOW_SIZE); + hdr->Checksum = 0; + hdr->UrgentPtr = 0; + + hdr->Checksum = Ipv4::PseudoHeaderChecksum( + conn->LocalIp, conn->RemoteIp, Ipv4::PROTO_TCP, + HEADER_SIZE, packet, HEADER_SIZE); + + Ipv4::Send(conn->RemoteIp, Ipv4::PROTO_TCP, packet, HEADER_SIZE); + } + + // Wait for ACK to complete the handshake + for (int i = 0; i < 100; i++) { + if (conn->CurrentState == State::Established) { + return conn; + } + Timekeeping::Sleep(50); + } + + // Timed out waiting for ACK + conn->Active = false; + return nullptr; + } + listener->Lock.Release(); + Timekeeping::Sleep(10); + } + } + + Connection* Connect(uint32_t destIp, uint16_t destPort, uint16_t srcPort) { + g_connectionsLock.Acquire(); + Connection* conn = AllocateConnection(); + g_connectionsLock.Release(); + + if (conn == nullptr) { + return nullptr; + } + + conn->LocalIp = Net::GetIpAddress(); + conn->LocalPort = srcPort; + conn->RemoteIp = destIp; + conn->RemotePort = destPort; + + uint32_t isn = GenerateISN(); + conn->SendNext = isn + 1; + conn->SendUnack = isn; + conn->CurrentState = State::SynSent; + + // Send SYN + { + uint8_t packet[HEADER_SIZE]; + Header* hdr = (Header*)packet; + + hdr->SrcPort = Htons(conn->LocalPort); + hdr->DstPort = Htons(conn->RemotePort); + hdr->SeqNum = Htonl(isn); + hdr->AckNum = 0; + hdr->DataOffset = (HEADER_SIZE / 4) << 4; + hdr->Flags = FLAG_SYN; + hdr->Window = Htons(WINDOW_SIZE); + hdr->Checksum = 0; + hdr->UrgentPtr = 0; + + hdr->Checksum = Ipv4::PseudoHeaderChecksum( + conn->LocalIp, conn->RemoteIp, Ipv4::PROTO_TCP, + HEADER_SIZE, packet, HEADER_SIZE); + + Ipv4::Send(conn->RemoteIp, Ipv4::PROTO_TCP, packet, HEADER_SIZE); + } + + // Wait for SYN-ACK + for (int attempt = 0; attempt < MAX_RETRANSMITS; attempt++) { + for (int i = 0; i < 20; i++) { + if (conn->CurrentState == State::Established) { + return conn; + } + Timekeeping::Sleep(50); + } + + if (conn->CurrentState == State::SynSent) { + // Retransmit SYN + uint8_t packet[HEADER_SIZE]; + Header* hdr = (Header*)packet; + + hdr->SrcPort = Htons(conn->LocalPort); + hdr->DstPort = Htons(conn->RemotePort); + hdr->SeqNum = Htonl(isn); + hdr->AckNum = 0; + hdr->DataOffset = (HEADER_SIZE / 4) << 4; + hdr->Flags = FLAG_SYN; + hdr->Window = Htons(WINDOW_SIZE); + hdr->Checksum = 0; + hdr->UrgentPtr = 0; + + hdr->Checksum = Ipv4::PseudoHeaderChecksum( + conn->LocalIp, conn->RemoteIp, Ipv4::PROTO_TCP, + HEADER_SIZE, packet, HEADER_SIZE); + + Ipv4::Send(conn->RemoteIp, Ipv4::PROTO_TCP, packet, HEADER_SIZE); + } + } + + // Failed to connect + conn->Active = false; + return nullptr; + } + + int Send(Connection* conn, const uint8_t* data, uint16_t length) { + if (conn == nullptr || conn->CurrentState != State::Established) { + return -1; + } + + conn->Lock.Acquire(); + + // Send data in segments up to MSS (we use a simple 1460 byte MSS) + constexpr uint16_t MSS = 1460; + uint16_t sent = 0; + + while (sent < length) { + uint16_t segLen = length - sent; + if (segLen > MSS) { + segLen = MSS; + } + + bool ok = SendSegment(conn, FLAG_ACK | FLAG_PSH, data + sent, segLen); + if (!ok) { + conn->Lock.Release(); + return sent > 0 ? sent : -1; + } + + conn->SendNext += segLen; + + // Store for retransmission + if (segLen <= sizeof(conn->RetransmitBuffer)) { + memcpy(conn->RetransmitBuffer, data + sent, segLen); + conn->RetransmitLen = segLen; + conn->RetransmitTime = Timekeeping::GetMilliseconds(); + conn->RetransmitCount = 0; + } + + sent += segLen; + } + + // Simple wait for ACK with retransmission + uint64_t startTime = Timekeeping::GetMilliseconds(); + while (conn->SendUnack != conn->SendNext) { + uint64_t now = Timekeeping::GetMilliseconds(); + if ((now - startTime) > (RETRANSMIT_TIMEOUT_MS * MAX_RETRANSMITS)) { + break; // Give up + } + if ((now - conn->RetransmitTime) > RETRANSMIT_TIMEOUT_MS && conn->RetransmitLen > 0) { + conn->RetransmitCount++; + if (conn->RetransmitCount > MAX_RETRANSMITS) { + break; + } + // Retransmit: rewind SendNext temporarily + uint32_t savedNext = conn->SendNext; + conn->SendNext = conn->SendUnack; + SendSegment(conn, FLAG_ACK | FLAG_PSH, + conn->RetransmitBuffer, conn->RetransmitLen); + conn->SendNext = savedNext; + conn->RetransmitTime = now; + } + Timekeeping::Sleep(10); + } + + conn->Lock.Release(); + return sent; + } + + int Receive(Connection* conn, uint8_t* buffer, uint16_t bufferSize) { + if (conn == nullptr) { + return -1; + } + + // Block until data is available or connection is closing + while (true) { + conn->Lock.Acquire(); + + if (conn->RecvCount > 0) { + uint16_t toRead = conn->RecvCount; + if (toRead > bufferSize) { + toRead = bufferSize; + } + + for (uint16_t i = 0; i < toRead; i++) { + buffer[i] = conn->RecvBuffer[conn->RecvHead]; + conn->RecvHead = (conn->RecvHead + 1) % RECV_BUFFER_SIZE; + } + conn->RecvCount -= toRead; + + conn->Lock.Release(); + return toRead; + } + + if (conn->CurrentState == State::CloseWait || + conn->CurrentState == State::Closed || + conn->CurrentState == State::TimeWait) { + conn->Lock.Release(); + return 0; // Connection closed + } + + conn->Lock.Release(); + Timekeeping::Sleep(10); + } + } + + void Close(Connection* conn) { + if (conn == nullptr) { + return; + } + + conn->Lock.Acquire(); + + switch (conn->CurrentState) { + case State::Established: { + conn->CurrentState = State::FinWait1; + SendSegment(conn, FLAG_FIN | FLAG_ACK, nullptr, 0); + conn->SendNext++; + conn->Lock.Release(); + + // Wait for close to complete + for (int i = 0; i < 100; i++) { + if (conn->CurrentState == State::TimeWait || + conn->CurrentState == State::Closed) { + break; + } + Timekeeping::Sleep(50); + } + conn->Active = false; + return; + } + + case State::CloseWait: { + conn->CurrentState = State::LastAck; + SendSegment(conn, FLAG_FIN | FLAG_ACK, nullptr, 0); + conn->SendNext++; + conn->Lock.Release(); + + // Wait for final ACK + for (int i = 0; i < 100; i++) { + if (conn->CurrentState == State::Closed) { + break; + } + Timekeeping::Sleep(50); + } + conn->Active = false; + return; + } + + case State::Listen: + case State::SynSent: { + conn->CurrentState = State::Closed; + conn->Active = false; + conn->Lock.Release(); + return; + } + + default: + conn->Lock.Release(); + conn->Active = false; + return; + } + } + + State GetState(Connection* conn) { + if (conn == nullptr) { + return State::Closed; + } + return conn->CurrentState; + } + +} diff --git a/kernel/src/Net/Tcp.hpp b/kernel/src/Net/Tcp.hpp new file mode 100644 index 0000000..24de7d8 --- /dev/null +++ b/kernel/src/Net/Tcp.hpp @@ -0,0 +1,80 @@ +/* + * Tcp.hpp + * Transmission Control Protocol + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include +#include + +namespace Net::Tcp { + + constexpr uint16_t HEADER_SIZE = 20; // Without options + + // TCP flags + constexpr uint8_t FLAG_FIN = 0x01; + constexpr uint8_t FLAG_SYN = 0x02; + constexpr uint8_t FLAG_RST = 0x04; + constexpr uint8_t FLAG_PSH = 0x08; + constexpr uint8_t FLAG_ACK = 0x10; + + struct Header { + uint16_t SrcPort; + uint16_t DstPort; + uint32_t SeqNum; + uint32_t AckNum; + uint8_t DataOffset; // Upper 4 bits = offset in 32-bit words + uint8_t Flags; + uint16_t Window; + uint16_t Checksum; + uint16_t UrgentPtr; + } __attribute__((packed)); + + // TCP connection states + enum class State { + Closed, + Listen, + SynSent, + SynReceived, + Established, + FinWait1, + FinWait2, + CloseWait, + LastAck, + TimeWait + }; + + // Opaque connection handle + struct Connection; + + // Initialize the TCP subsystem + void Initialize(); + + // Handle an incoming TCP segment (called by IPv4 layer) + void OnPacketReceived(uint32_t srcIp, uint32_t dstIp, const uint8_t* data, uint16_t length); + + // Listen on a port. Returns a connection handle in Listen state. + Connection* Listen(uint16_t port); + + // Accept an incoming connection on a listening socket. + // Blocks until a connection arrives. Returns a new connection in Established state. + Connection* Accept(Connection* listener); + + // Actively connect to a remote host:port. Returns connection in Established state or nullptr. + Connection* Connect(uint32_t destIp, uint16_t destPort, uint16_t srcPort); + + // Send data on an established connection. Returns number of bytes sent. + int Send(Connection* conn, const uint8_t* data, uint16_t length); + + // Receive data from an established connection. Returns number of bytes received. + // Blocks until data is available or connection is closed. + int Receive(Connection* conn, uint8_t* buffer, uint16_t bufferSize); + + // Close a TCP connection gracefully + void Close(Connection* conn); + + // Get the state of a connection + State GetState(Connection* conn); + +} diff --git a/kernel/src/Net/Udp.cpp b/kernel/src/Net/Udp.cpp new file mode 100644 index 0000000..1840317 --- /dev/null +++ b/kernel/src/Net/Udp.cpp @@ -0,0 +1,127 @@ +/* + * Udp.cpp + * User Datagram Protocol + * Copyright (c) 2025 Daniel Hammer +*/ + +#include "Udp.hpp" +#include +#include +#include +#include +#include +#include + +using namespace Kt; + +namespace Net::Udp { + + struct PortBinding { + uint16_t Port; + RecvCallback Callback; + bool Active; + }; + + static constexpr uint32_t MAX_BINDINGS = 16; + static PortBinding g_bindings[MAX_BINDINGS] = {}; + + void Initialize() { + for (uint32_t i = 0; i < MAX_BINDINGS; i++) { + g_bindings[i].Active = false; + } + KernelLogStream(OK, "Net") << "UDP initialized"; + } + + void OnPacketReceived(uint32_t srcIp, uint32_t dstIp, const uint8_t* data, uint16_t length) { + if (length < HEADER_SIZE) { + return; + } + + const Header* hdr = (const Header*)data; + uint16_t srcPort = Ntohs(hdr->SrcPort); + uint16_t dstPort = Ntohs(hdr->DstPort); + uint16_t udpLen = Ntohs(hdr->Length); + + if (udpLen < HEADER_SIZE || udpLen > length) { + return; + } + + // Verify checksum if present + if (hdr->Checksum != 0) { + uint16_t check = Ipv4::PseudoHeaderChecksum(srcIp, dstIp, Ipv4::PROTO_UDP, + udpLen, data, udpLen); + if (check != 0) { + return; + } + } + + const uint8_t* payload = data + HEADER_SIZE; + uint16_t payloadLen = udpLen - HEADER_SIZE; + + // Dispatch to bound callback + for (uint32_t i = 0; i < MAX_BINDINGS; i++) { + if (g_bindings[i].Active && g_bindings[i].Port == dstPort) { + g_bindings[i].Callback(srcIp, srcPort, payload, payloadLen); + return; + } + } + } + + bool Send(uint32_t destIp, uint16_t srcPort, uint16_t destPort, + const uint8_t* payload, uint16_t payloadLen) { + uint16_t udpLen = HEADER_SIZE + payloadLen; + uint8_t packet[1500]; + + if (udpLen > sizeof(packet)) { + return false; + } + + Header* hdr = (Header*)packet; + hdr->SrcPort = Htons(srcPort); + hdr->DstPort = Htons(destPort); + hdr->Length = Htons(udpLen); + hdr->Checksum = 0; + + memcpy(packet + HEADER_SIZE, payload, payloadLen); + + // Calculate checksum with pseudo-header + hdr->Checksum = Ipv4::PseudoHeaderChecksum( + Net::GetIpAddress(), destIp, Ipv4::PROTO_UDP, + udpLen, packet, udpLen); + if (hdr->Checksum == 0) { + hdr->Checksum = 0xFFFF; // RFC 768: zero checksum transmitted as all ones + } + + return Ipv4::Send(destIp, Ipv4::PROTO_UDP, packet, udpLen); + } + + bool Bind(uint16_t port, RecvCallback callback) { + // Check for duplicate + for (uint32_t i = 0; i < MAX_BINDINGS; i++) { + if (g_bindings[i].Active && g_bindings[i].Port == port) { + return false; + } + } + + // Find empty slot + for (uint32_t i = 0; i < MAX_BINDINGS; i++) { + if (!g_bindings[i].Active) { + g_bindings[i].Port = port; + g_bindings[i].Callback = callback; + g_bindings[i].Active = true; + return true; + } + } + return false; + } + + void Unbind(uint16_t port) { + for (uint32_t i = 0; i < MAX_BINDINGS; i++) { + if (g_bindings[i].Active && g_bindings[i].Port == port) { + g_bindings[i].Active = false; + return; + } + } + } + +} diff --git a/kernel/src/Net/Udp.hpp b/kernel/src/Net/Udp.hpp new file mode 100644 index 0000000..6090182 --- /dev/null +++ b/kernel/src/Net/Udp.hpp @@ -0,0 +1,40 @@ +/* + * Udp.hpp + * User Datagram Protocol + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include + +namespace Net::Udp { + + constexpr uint16_t HEADER_SIZE = 8; + + struct Header { + uint16_t SrcPort; + uint16_t DstPort; + uint16_t Length; + uint16_t Checksum; + } __attribute__((packed)); + + // Callback type for receiving UDP data + using RecvCallback = void(*)(uint32_t srcIp, uint16_t srcPort, const uint8_t* data, uint16_t length); + + // Initialize the UDP subsystem + void Initialize(); + + // Handle an incoming UDP packet (called by IPv4 layer) + void OnPacketReceived(uint32_t srcIp, uint32_t dstIp, const uint8_t* data, uint16_t length); + + // Send a UDP datagram + bool Send(uint32_t destIp, uint16_t srcPort, uint16_t destPort, + const uint8_t* payload, uint16_t payloadLen); + + // Bind a callback to a local port. Returns true on success. + bool Bind(uint16_t port, RecvCallback callback); + + // Unbind a port + void Unbind(uint16_t port); + +} diff --git a/scripts/net-setup.sh b/scripts/net-setup.sh new file mode 100755 index 0000000..6993132 --- /dev/null +++ b/scripts/net-setup.sh @@ -0,0 +1,70 @@ +#!/bin/bash +# Sets up a bridge + TAP device so QEMU guests appear on the local LAN. +# Requires root. Idempotent — safe to run multiple times. + +set -e + +BRIDGE=br0 +TAP=tap0 +PHYS=enp0s31f6 +USER=$(logname 2>/dev/null || echo "${SUDO_USER:-daniel-hammer}") + +# Check if physical interface is already in the bridge (properly) +PHYS_MASTER=$(ip -j link show dev "$PHYS" 2>/dev/null | grep -oP '"master"\s*:\s*"\K[^"]+' || true) + +if [ "$PHYS_MASTER" = "$BRIDGE" ] && ip link show "$TAP" &>/dev/null; then + echo "Bridge $BRIDGE with $PHYS and $TAP already set up." + exit 0 +fi + +echo "Creating bridge $BRIDGE..." +ip link add name "$BRIDGE" type bridge 2>/dev/null || true +ip link set "$BRIDGE" up + +# Tell NetworkManager to leave our interfaces alone (if NM is running) +if command -v nmcli &>/dev/null; then + nmcli device set "$PHYS" managed no 2>/dev/null || true + nmcli device set "$BRIDGE" managed no 2>/dev/null || true +fi + +# Move physical interface into bridge (if not already) +if [ "$PHYS_MASTER" != "$BRIDGE" ]; then + echo "Moving $PHYS into bridge $BRIDGE..." + + # Grab current IP config before moving + ADDR=$(ip -4 addr show dev "$PHYS" | grep -oP 'inet \K[\d.]+/\d+' | head -1) + GW=$(ip route show default dev "$PHYS" | grep -oP 'via \K[\d.]+' | head -1) + + ip addr flush dev "$PHYS" + ip link set "$PHYS" master "$BRIDGE" + + # Apply IP config to bridge + if [ -n "$ADDR" ]; then + ip addr add "$ADDR" dev "$BRIDGE" 2>/dev/null || true + echo "Assigned $ADDR to $BRIDGE" + fi + if [ -n "$GW" ]; then + ip route add default via "$GW" dev "$BRIDGE" 2>/dev/null || true + echo "Set default gateway $GW via $BRIDGE" + fi +else + echo "$PHYS already in $BRIDGE" +fi + +# Create TAP device (if not already) +if ! ip link show "$TAP" &>/dev/null; then + echo "Creating TAP $TAP for user $USER..." + ip tuntap add dev "$TAP" mode tap user "$USER" + ip link set "$TAP" master "$BRIDGE" + ip link set "$TAP" up +else + echo "TAP $TAP already exists" + ip link set "$TAP" up 2>/dev/null || true +fi + +if command -v nmcli &>/dev/null; then + nmcli device set "$TAP" managed no 2>/dev/null || true +fi + +echo "Network bridge setup complete: $PHYS -> $BRIDGE <- $TAP" +ip -4 addr show dev "$BRIDGE" | head -3 diff --git a/scripts/net-teardown.sh b/scripts/net-teardown.sh new file mode 100755 index 0000000..7b26708 --- /dev/null +++ b/scripts/net-teardown.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# Tears down the bridge + TAP setup and restores the physical interface. +# Requires root. + +set -e + +BRIDGE=br0 +TAP=tap0 +PHYS=enp0s31f6 + +if ! ip link show "$TAP" &>/dev/null; then + echo "TAP $TAP doesn't exist, nothing to tear down." + exit 0 +fi + +echo "Tearing down network bridge..." + +# Grab IP config from bridge before removing +ADDR=$(ip -4 addr show dev "$BRIDGE" 2>/dev/null | grep -oP 'inet \K[\d.]+/\d+') +GW=$(ip route show default dev "$BRIDGE" 2>/dev/null | grep -oP 'via \K[\d.]+' | head -1) + +# Remove TAP +ip link set "$TAP" down 2>/dev/null || true +ip link delete "$TAP" 2>/dev/null || true + +# Remove physical interface from bridge and restore its config +ip link set "$PHYS" nomaster 2>/dev/null || true +ip link delete "$BRIDGE" type bridge 2>/dev/null || true + +# Restore IP config to physical interface +if [ -n "$ADDR" ]; then + ip addr add "$ADDR" dev "$PHYS" 2>/dev/null || true +fi +if [ -n "$GW" ]; then + ip route add default via "$GW" dev "$PHYS" 2>/dev/null || true +fi + +echo "Network bridge torn down, $PHYS restored."