feat: add ethernet & TCP/IP stack
This commit is contained in:
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user