feat: add ethernet & TCP/IP stack

This commit is contained in:
2026-02-17 13:19:11 +01:00
parent f7e6ce70a0
commit 20fa8a9be2
23 changed files with 2409 additions and 0 deletions
+12
View File
@@ -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
+446
View File
@@ -0,0 +1,446 @@
/*
* E1000.cpp
* Intel 82540EM (E1000) Ethernet driver
* Copyright (c) 2025 Daniel Hammer
*/
#include "E1000.hpp"
#include <Pci/Pci.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Memory/HHDM.hpp>
#include <Memory/Paging.hpp>
#include <Memory/PageFrameAllocator.hpp>
#include <Libraries/Memory.hpp>
#include <Hal/Apic/Interrupts.hpp>
#include <Hal/Apic/IoApic.hpp>
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;
}
};
+120
View File
@@ -0,0 +1,120 @@
/*
* E1000.hpp
* Intel 82540EM (E1000) Ethernet driver
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include <cstdint>
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);
};
+5
View File
@@ -33,6 +33,8 @@
#include <Drivers/PS2/PS2Controller.hpp>
#include <Drivers/PS2/Keyboard.hpp>
#include <Drivers/PS2/Mouse.hpp>
#include <Drivers/Net/E1000.hpp>
#include <Net/Net.hpp>
#include <CppLib/BoxUI.hpp>
#include <Graphics/Cursor.hpp>
@@ -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
+151
View File
@@ -0,0 +1,151 @@
/*
* Arp.cpp
* Address Resolution Protocol
* Copyright (c) 2025 Daniel Hammer
*/
#include "Arp.hpp"
#include <Net/ByteOrder.hpp>
#include <Net/Ethernet.hpp>
#include <Net/NetConfig.hpp>
#include <Drivers/Net/E1000.hpp>
#include <Libraries/Memory.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Timekeeping/ApicTimer.hpp>
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));
}
}
+43
View File
@@ -0,0 +1,43 @@
/*
* Arp.hpp
* Address Resolution Protocol
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include <cstdint>
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);
}
+31
View File
@@ -0,0 +1,31 @@
/*
* ByteOrder.hpp
* Network byte order conversion utilities
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include <cstdint>
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);
}
}
+65
View File
@@ -0,0 +1,65 @@
/*
* Ethernet.cpp
* Ethernet frame layer
* Copyright (c) 2025 Daniel Hammer
*/
#include "Ethernet.hpp"
#include <Net/ByteOrder.hpp>
#include <Net/Arp.hpp>
#include <Net/Ipv4.hpp>
#include <Drivers/Net/E1000.hpp>
#include <Libraries/Memory.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
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;
}
}
}
+36
View File
@@ -0,0 +1,36 @@
/*
* Ethernet.hpp
* Ethernet frame layer
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include <cstdint>
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);
}
+60
View File
@@ -0,0 +1,60 @@
/*
* Icmp.cpp
* Internet Control Message Protocol
* Copyright (c) 2025 Daniel Hammer
*/
#include "Icmp.hpp"
#include <Net/Ipv4.hpp>
#include <Net/ByteOrder.hpp>
#include <Libraries/Memory.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
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);
}
}
}
+29
View File
@@ -0,0 +1,29 @@
/*
* Icmp.hpp
* Internet Control Message Protocol
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include <cstdint>
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);
}
+188
View File
@@ -0,0 +1,188 @@
/*
* Ipv4.cpp
* Internet Protocol version 4
* Copyright (c) 2025 Daniel Hammer
*/
#include "Ipv4.hpp"
#include <Net/ByteOrder.hpp>
#include <Net/Ethernet.hpp>
#include <Net/Arp.hpp>
#include <Net/Icmp.hpp>
#include <Net/Udp.hpp>
#include <Net/Tcp.hpp>
#include <Net/NetConfig.hpp>
#include <Libraries/Memory.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Timekeeping/ApicTimer.hpp>
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);
}
}
+49
View File
@@ -0,0 +1,49 @@
/*
* Ipv4.hpp
* Internet Protocol version 4
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include <cstdint>
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);
}
+46
View File
@@ -0,0 +1,46 @@
/*
* Net.cpp
* Network stack initialization
* Copyright (c) 2025 Daniel Hammer
*/
#include "Net.hpp"
#include <Net/Ethernet.hpp>
#include <Net/Arp.hpp>
#include <Net/Ipv4.hpp>
#include <Net/Icmp.hpp>
#include <Net/Udp.hpp>
#include <Net/Tcp.hpp>
#include <Net/NetConfig.hpp>
#include <Drivers/Net/E1000.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
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";
}
}
+14
View File
@@ -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();
}
+36
View File
@@ -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;
}
}
+37
View File
@@ -0,0 +1,37 @@
/*
* NetConfig.hpp
* Network configuration (static IP, gateway, etc.)
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include <cstdint>
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);
}
+686
View File
@@ -0,0 +1,686 @@
/*
* Tcp.cpp
* Transmission Control Protocol
* Copyright (c) 2025 Daniel Hammer
*/
#include "Tcp.hpp"
#include <Net/Ipv4.hpp>
#include <Net/ByteOrder.hpp>
#include <Net/NetConfig.hpp>
#include <Libraries/Memory.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <CppLib/Spinlock.hpp>
#include <Timekeeping/ApicTimer.hpp>
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;
}
}
+80
View File
@@ -0,0 +1,80 @@
/*
* Tcp.hpp
* Transmission Control Protocol
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include <cstdint>
#include <CppLib/Spinlock.hpp>
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);
}
+127
View File
@@ -0,0 +1,127 @@
/*
* Udp.cpp
* User Datagram Protocol
* Copyright (c) 2025 Daniel Hammer
*/
#include "Udp.hpp"
#include <Net/Ipv4.hpp>
#include <Net/ByteOrder.hpp>
#include <Net/NetConfig.hpp>
#include <Libraries/Memory.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
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;
}
}
}
}
+40
View File
@@ -0,0 +1,40 @@
/*
* Udp.hpp
* User Datagram Protocol
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include <cstdint>
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);
}
+70
View File
@@ -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
+38
View File
@@ -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."