feat: Sockets, user-mode networking, IRC client application

This commit is contained in:
2026-02-18 18:35:34 +01:00
parent dfa86b272c
commit 1a5d943649
15 changed files with 1635 additions and 14 deletions
+53 -1
View File
@@ -18,6 +18,7 @@
#include <Libraries/String.hpp>
#include <Drivers/PS2/Keyboard.hpp>
#include <Net/Icmp.hpp>
#include <Net/Socket.hpp>
#include <Net/ByteOrder.hpp>
#include <Hal/MSR.hpp>
#include <Hal/GDT.hpp>
@@ -268,6 +269,40 @@ namespace Zenith {
out->Second = dt.Second;
}
// ---- Socket syscalls ----
static int Sys_Socket(int type) {
return Net::Socket::Create(type, Sched::GetCurrentPid());
}
static int Sys_Connect(int fd, uint32_t ip, uint16_t port) {
return Net::Socket::Connect(fd, ip, port, Sched::GetCurrentPid());
}
static int Sys_Bind(int fd, uint16_t port) {
return Net::Socket::Bind(fd, port, Sched::GetCurrentPid());
}
static int Sys_Listen(int fd) {
return Net::Socket::Listen(fd, Sched::GetCurrentPid());
}
static int Sys_Accept(int fd) {
return Net::Socket::Accept(fd, Sched::GetCurrentPid());
}
static int Sys_Send(int fd, const uint8_t* data, uint32_t len) {
return Net::Socket::Send(fd, data, len, Sched::GetCurrentPid());
}
static int Sys_Recv(int fd, uint8_t* buf, uint32_t maxLen) {
return Net::Socket::Recv(fd, buf, maxLen, Sched::GetCurrentPid());
}
static void Sys_CloseSock(int fd) {
Net::Socket::Close(fd, Sched::GetCurrentPid());
}
static void Sys_Reset() {
/*
Triple fault for now; TODO: implement UEFI runtime function for clean reboot.
@@ -364,6 +399,23 @@ namespace Zenith {
case SYS_GETTIME:
Sys_GetTime((DateTime*)frame->arg1);
return 0;
case SYS_SOCKET:
return (int64_t)Sys_Socket((int)frame->arg1);
case SYS_CONNECT:
return (int64_t)Sys_Connect((int)frame->arg1, (uint32_t)frame->arg2, (uint16_t)frame->arg3);
case SYS_BIND:
return (int64_t)Sys_Bind((int)frame->arg1, (uint16_t)frame->arg2);
case SYS_LISTEN:
return (int64_t)Sys_Listen((int)frame->arg1);
case SYS_ACCEPT:
return (int64_t)Sys_Accept((int)frame->arg1);
case SYS_SEND:
return (int64_t)Sys_Send((int)frame->arg1, (const uint8_t*)frame->arg2, (uint32_t)frame->arg3);
case SYS_RECV:
return (int64_t)Sys_Recv((int)frame->arg1, (uint8_t*)frame->arg2, (uint32_t)frame->arg3);
case SYS_CLOSESOCK:
Sys_CloseSock((int)frame->arg1);
return 0;
default:
return -1;
}
@@ -390,7 +442,7 @@ namespace Zenith {
Hal::WriteMSR(Hal::IA32_FMASK, 0x200);
Kt::KernelLogStream(Kt::OK, "Syscall") << "SYSCALL/SYSRET initialized (LSTAR="
<< kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 29 syscalls)";
<< kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 37 syscalls)";
}
}
+10
View File
@@ -40,6 +40,16 @@ namespace Zenith {
static constexpr uint64_t SYS_RESET = 26;
static constexpr uint64_t SYS_SHUTDOWN = 27;
static constexpr uint64_t SYS_GETTIME = 28;
static constexpr uint64_t SYS_SOCKET = 29;
static constexpr uint64_t SYS_CONNECT = 30;
static constexpr uint64_t SYS_BIND = 31;
static constexpr uint64_t SYS_LISTEN = 32;
static constexpr uint64_t SYS_ACCEPT = 33;
static constexpr uint64_t SYS_SEND = 34;
static constexpr uint64_t SYS_RECV = 35;
static constexpr uint64_t SYS_CLOSESOCK = 36;
static constexpr int SOCK_TCP = 1;
struct DateTime {
uint16_t Year;
+2
View File
@@ -11,6 +11,7 @@
#include <Net/Icmp.hpp>
#include <Net/Udp.hpp>
#include <Net/Tcp.hpp>
#include <Net/Socket.hpp>
#include <Net/NetConfig.hpp>
#include <Drivers/Net/E1000.hpp>
#include <Terminal/Terminal.hpp>
@@ -33,6 +34,7 @@ namespace Net {
Icmp::Initialize();
Udp::Initialize();
Tcp::Initialize();
Socket::Initialize();
// Hook E1000 RX to our Ethernet dispatcher
Drivers::Net::E1000::SetRxCallback(Ethernet::OnFrameReceived);
+145
View File
@@ -0,0 +1,145 @@
/*
* Socket.cpp
* Socket descriptor table wrapping kernel TCP layer
* Copyright (c) 2025 Daniel Hammer
*/
#include "Socket.hpp"
#include <Net/Tcp.hpp>
#include <Net/NetConfig.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
using namespace Kt;
namespace Net::Socket {
static SocketEntry g_sockets[MAX_SOCKETS] = {};
static uint16_t g_nextEphemeralPort = 49152;
static uint16_t AllocEphemeralPort() {
uint16_t port = g_nextEphemeralPort++;
if (g_nextEphemeralPort == 0) g_nextEphemeralPort = 49152;
return port;
}
static bool ValidFd(int fd, int pid) {
if (fd < 0 || fd >= MAX_SOCKETS) return false;
if (!g_sockets[fd].Active) return false;
if (g_sockets[fd].OwnerPid != pid) return false;
return true;
}
void Initialize() {
for (int i = 0; i < MAX_SOCKETS; i++) {
g_sockets[i].Active = false;
}
KernelLogStream(OK, "Net") << "Socket table initialized";
}
int Create(int type, int pid) {
if (type != SOCK_TCP) return -1;
for (int i = 0; i < MAX_SOCKETS; i++) {
if (!g_sockets[i].Active) {
g_sockets[i].Active = true;
g_sockets[i].Type = type;
g_sockets[i].OwnerPid = pid;
g_sockets[i].TcpConn = nullptr;
g_sockets[i].LocalPort = 0;
return i;
}
}
return -1;
}
int Connect(int fd, uint32_t ip, uint16_t port, int pid) {
if (!ValidFd(fd, pid)) return -1;
if (g_sockets[fd].TcpConn != nullptr) return -1;
uint16_t srcPort = AllocEphemeralPort();
g_sockets[fd].LocalPort = srcPort;
Tcp::Connection* conn = Tcp::Connect(ip, port, srcPort);
if (conn == nullptr) return -1;
g_sockets[fd].TcpConn = conn;
return 0;
}
int Bind(int fd, uint16_t port, int pid) {
if (!ValidFd(fd, pid)) return -1;
g_sockets[fd].LocalPort = port;
return 0;
}
int Listen(int fd, int pid) {
if (!ValidFd(fd, pid)) return -1;
if (g_sockets[fd].LocalPort == 0) return -1;
if (g_sockets[fd].TcpConn != nullptr) return -1;
Tcp::Connection* conn = Tcp::Listen(g_sockets[fd].LocalPort);
if (conn == nullptr) return -1;
g_sockets[fd].TcpConn = conn;
return 0;
}
int Accept(int fd, int pid) {
if (!ValidFd(fd, pid)) return -1;
if (g_sockets[fd].TcpConn == nullptr) return -1;
Tcp::Connection* clientConn = Tcp::Accept(g_sockets[fd].TcpConn);
if (clientConn == nullptr) return -1;
// Allocate a new socket entry for the accepted connection
for (int i = 0; i < MAX_SOCKETS; i++) {
if (!g_sockets[i].Active) {
g_sockets[i].Active = true;
g_sockets[i].Type = SOCK_TCP;
g_sockets[i].OwnerPid = pid;
g_sockets[i].TcpConn = clientConn;
g_sockets[i].LocalPort = g_sockets[fd].LocalPort;
return i;
}
}
// No free socket slot — close the accepted connection
Tcp::Close(clientConn);
return -1;
}
int Send(int fd, const uint8_t* data, uint32_t len, int pid) {
if (!ValidFd(fd, pid)) return -1;
if (g_sockets[fd].TcpConn == nullptr) return -1;
return Tcp::Send(g_sockets[fd].TcpConn, data, (uint16_t)len);
}
int Recv(int fd, uint8_t* buf, uint32_t maxLen, int pid) {
if (!ValidFd(fd, pid)) return -1;
if (g_sockets[fd].TcpConn == nullptr) return -1;
return Tcp::ReceiveNonBlocking(g_sockets[fd].TcpConn, buf, (uint16_t)maxLen);
}
void Close(int fd, int pid) {
if (!ValidFd(fd, pid)) return;
if (g_sockets[fd].TcpConn != nullptr) {
Tcp::Close(g_sockets[fd].TcpConn);
g_sockets[fd].TcpConn = nullptr;
}
g_sockets[fd].Active = false;
}
void CleanupProcess(int pid) {
for (int i = 0; i < MAX_SOCKETS; i++) {
if (g_sockets[i].Active && g_sockets[i].OwnerPid == pid) {
if (g_sockets[i].TcpConn != nullptr) {
Tcp::Close(g_sockets[i].TcpConn);
g_sockets[i].TcpConn = nullptr;
}
g_sockets[i].Active = false;
}
}
}
}
+54
View File
@@ -0,0 +1,54 @@
/*
* Socket.hpp
* Socket descriptor table for userspace TCP access
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include <cstdint>
#include <Net/Tcp.hpp>
namespace Net::Socket {
static constexpr int SOCK_TCP = 1;
static constexpr int SOCK_UDP = 2;
static constexpr int MAX_SOCKETS = 64;
struct SocketEntry {
bool Active;
int Type;
int OwnerPid;
Tcp::Connection* TcpConn;
uint16_t LocalPort;
};
void Initialize();
// Create a socket of the given type. Returns fd or -1.
int Create(int type, int pid);
// Connect socket fd to remote ip:port. Returns 0 or -1.
int Connect(int fd, uint32_t ip, uint16_t port, int pid);
// Bind socket fd to a local port. Returns 0 or -1.
int Bind(int fd, uint16_t port, int pid);
// Start listening on a bound socket. Returns 0 or -1.
int Listen(int fd, int pid);
// Accept an incoming connection. Returns new fd or -1.
int Accept(int fd, int pid);
// Send data on a connected socket. Returns bytes sent or -1.
int Send(int fd, const uint8_t* data, uint32_t len, int pid);
// Receive data from a connected socket. Returns bytes received, 0 on close, or -1.
int Recv(int fd, uint8_t* buf, uint32_t maxLen, int pid);
// Close a socket.
void Close(int fd, int pid);
// Close all sockets owned by a process (called on process exit).
void CleanupProcess(int pid);
}
+65 -6
View File
@@ -523,9 +523,11 @@ namespace Net::Tcp {
return -1;
}
// Phase 1: Send data segments with interrupts disabled (lock-safe)
uint64_t flags;
asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory");
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;
@@ -538,6 +540,7 @@ namespace Net::Tcp {
bool ok = SendSegment(conn, FLAG_ACK | FLAG_PSH, data + sent, segLen);
if (!ok) {
conn->Lock.Release();
asm volatile("push %0; popfq" :: "r"(flags) : "memory");
return sent > 0 ? sent : -1;
}
@@ -554,30 +557,36 @@ namespace Net::Tcp {
sent += segLen;
}
// Simple wait for ACK with retransmission
conn->Lock.Release();
asm volatile("push %0; popfq" :: "r"(flags) : "memory");
// Phase 2: Wait for ACK without holding the lock (interrupts enabled)
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
break;
}
if ((now - conn->RetransmitTime) > RETRANSMIT_TIMEOUT_MS && conn->RetransmitLen > 0) {
conn->RetransmitCount++;
if (conn->RetransmitCount > MAX_RETRANSMITS) {
break;
}
// Retransmit: rewind SendNext temporarily
// Retransmit with brief interrupt-disabled window
asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory");
conn->Lock.Acquire();
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;
conn->RetransmitTime = Timekeeping::GetMilliseconds();
conn->Lock.Release();
asm volatile("push %0; popfq" :: "r"(flags) : "memory");
}
Timekeeping::Sleep(10);
}
conn->Lock.Release();
return sent;
}
@@ -588,6 +597,8 @@ namespace Net::Tcp {
// Block until data is available or connection is closing
while (true) {
uint64_t flags;
asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory");
conn->Lock.Acquire();
if (conn->RecvCount > 0) {
@@ -603,6 +614,7 @@ namespace Net::Tcp {
conn->RecvCount -= toRead;
conn->Lock.Release();
asm volatile("push %0; popfq" :: "r"(flags) : "memory");
return toRead;
}
@@ -610,19 +622,62 @@ namespace Net::Tcp {
conn->CurrentState == State::Closed ||
conn->CurrentState == State::TimeWait) {
conn->Lock.Release();
asm volatile("push %0; popfq" :: "r"(flags) : "memory");
return 0; // Connection closed
}
conn->Lock.Release();
asm volatile("push %0; popfq" :: "r"(flags) : "memory");
Timekeeping::Sleep(10);
}
}
int ReceiveNonBlocking(Connection* conn, uint8_t* buffer, uint16_t bufferSize) {
if (conn == nullptr) {
return -1;
}
// Disable interrupts while holding the lock to prevent deadlock
// with OnPacketReceived (called from the network interrupt handler)
uint64_t flags;
asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory");
conn->Lock.Acquire();
int result;
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;
result = toRead;
} else if (conn->CurrentState == State::CloseWait ||
conn->CurrentState == State::Closed ||
conn->CurrentState == State::TimeWait) {
result = -1; // Connection closed
} else {
result = 0; // No data available
}
conn->Lock.Release();
asm volatile("push %0; popfq" :: "r"(flags) : "memory");
return result;
}
void Close(Connection* conn) {
if (conn == nullptr) {
return;
}
uint64_t flags;
asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory");
conn->Lock.Acquire();
switch (conn->CurrentState) {
@@ -631,6 +686,7 @@ namespace Net::Tcp {
SendSegment(conn, FLAG_FIN | FLAG_ACK, nullptr, 0);
conn->SendNext++;
conn->Lock.Release();
asm volatile("push %0; popfq" :: "r"(flags) : "memory");
// Wait for close to complete
for (int i = 0; i < 100; i++) {
@@ -649,6 +705,7 @@ namespace Net::Tcp {
SendSegment(conn, FLAG_FIN | FLAG_ACK, nullptr, 0);
conn->SendNext++;
conn->Lock.Release();
asm volatile("push %0; popfq" :: "r"(flags) : "memory");
// Wait for final ACK
for (int i = 0; i < 100; i++) {
@@ -666,11 +723,13 @@ namespace Net::Tcp {
conn->CurrentState = State::Closed;
conn->Active = false;
conn->Lock.Release();
asm volatile("push %0; popfq" :: "r"(flags) : "memory");
return;
}
default:
conn->Lock.Release();
asm volatile("push %0; popfq" :: "r"(flags) : "memory");
conn->Active = false;
return;
}
+3
View File
@@ -71,6 +71,9 @@ namespace Net::Tcp {
// Blocks until data is available or connection is closed.
int Receive(Connection* conn, uint8_t* buffer, uint16_t bufferSize);
// Non-blocking receive. Returns bytes read, 0 if no data available, or -1 on closed/error.
int ReceiveNonBlocking(Connection* conn, uint8_t* buffer, uint16_t bufferSize);
// Close a TCP connection gracefully
void Close(Connection* conn);