feat: userspace overhaul, Intel GPU driver, and more
This commit is contained in:
@@ -0,0 +1,375 @@
|
||||
/*
|
||||
* Dns.cpp
|
||||
* DNS resolver (kernel-level, RFC 1035)
|
||||
* Copyright (c) 2025-2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "Dns.hpp"
|
||||
#include <Net/Udp.hpp>
|
||||
#include <Net/ByteOrder.hpp>
|
||||
#include <Net/NetConfig.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Libraries/String.hpp>
|
||||
#include <Timekeeping/ApicTimer.hpp>
|
||||
#include <Sched/Scheduler.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
|
||||
namespace Net::Dns {
|
||||
|
||||
// ---- DNS packet constants ----
|
||||
|
||||
static constexpr uint16_t DNS_PORT = 53;
|
||||
static constexpr uint16_t DNS_FLAGS_RD = 0x0100; // Recursion Desired
|
||||
static constexpr uint16_t DNS_QTYPE_A = 1;
|
||||
static constexpr uint16_t DNS_QCLASS_IN = 1;
|
||||
|
||||
// ---- Simple cache ----
|
||||
|
||||
static constexpr int CACHE_SIZE = 8;
|
||||
|
||||
struct CacheEntry {
|
||||
char hostname[128];
|
||||
uint32_t ip;
|
||||
uint32_t ttl; // TTL in seconds
|
||||
uint64_t timestamp; // ms when cached
|
||||
bool valid;
|
||||
};
|
||||
|
||||
static CacheEntry g_cache[CACHE_SIZE] = {};
|
||||
|
||||
static bool streq(const char* a, const char* b) {
|
||||
while (*a && *b) {
|
||||
if (*a != *b) return false;
|
||||
a++; b++;
|
||||
}
|
||||
return *a == *b;
|
||||
}
|
||||
|
||||
static uint32_t CacheLookup(const char* hostname) {
|
||||
uint64_t now = Timekeeping::GetMilliseconds();
|
||||
for (int i = 0; i < CACHE_SIZE; i++) {
|
||||
if (!g_cache[i].valid) continue;
|
||||
if (!streq(g_cache[i].hostname, hostname)) continue;
|
||||
// Check TTL
|
||||
uint64_t elapsed = (now - g_cache[i].timestamp) / 1000;
|
||||
if (elapsed < g_cache[i].ttl) {
|
||||
return g_cache[i].ip;
|
||||
}
|
||||
// Expired
|
||||
g_cache[i].valid = false;
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void CacheStore(const char* hostname, uint32_t ip, uint32_t ttl) {
|
||||
if (ttl == 0) ttl = 60; // Minimum 60s TTL
|
||||
|
||||
// Find free or oldest slot
|
||||
int slot = 0;
|
||||
uint64_t oldestTime = ~0ULL;
|
||||
for (int i = 0; i < CACHE_SIZE; i++) {
|
||||
if (!g_cache[i].valid) { slot = i; break; }
|
||||
if (g_cache[i].timestamp < oldestTime) {
|
||||
oldestTime = g_cache[i].timestamp;
|
||||
slot = i;
|
||||
}
|
||||
}
|
||||
|
||||
CacheEntry& e = g_cache[slot];
|
||||
// Copy hostname
|
||||
int len = 0;
|
||||
while (hostname[len] && len < 126) { e.hostname[len] = hostname[len]; len++; }
|
||||
e.hostname[len] = '\0';
|
||||
e.ip = ip;
|
||||
e.ttl = ttl;
|
||||
e.timestamp = Timekeeping::GetMilliseconds();
|
||||
e.valid = true;
|
||||
}
|
||||
|
||||
// ---- DNS query building ----
|
||||
|
||||
// Encode a hostname as DNS labels: "example.com" -> "\x07example\x03com\x00"
|
||||
// Returns number of bytes written, or 0 on error.
|
||||
static int EncodeName(const char* hostname, uint8_t* out, int maxLen) {
|
||||
int outPos = 0;
|
||||
const char* p = hostname;
|
||||
|
||||
while (*p) {
|
||||
// Find the next dot or end
|
||||
const char* dot = p;
|
||||
while (*dot && *dot != '.') dot++;
|
||||
int labelLen = (int)(dot - p);
|
||||
|
||||
if (labelLen == 0 || labelLen > 63) return 0;
|
||||
if (outPos + 1 + labelLen >= maxLen) return 0;
|
||||
|
||||
out[outPos++] = (uint8_t)labelLen;
|
||||
for (int i = 0; i < labelLen; i++) {
|
||||
out[outPos++] = (uint8_t)p[i];
|
||||
}
|
||||
|
||||
p = dot;
|
||||
if (*p == '.') p++;
|
||||
}
|
||||
|
||||
if (outPos >= maxLen) return 0;
|
||||
out[outPos++] = 0; // Root label terminator
|
||||
return outPos;
|
||||
}
|
||||
|
||||
// Build a DNS query packet. Returns total packet length, or 0 on error.
|
||||
static int BuildQuery(uint16_t id, const char* hostname, uint8_t* packet, int maxLen) {
|
||||
if (maxLen < 12) return 0;
|
||||
|
||||
// Header (12 bytes)
|
||||
packet[0] = (uint8_t)(id >> 8);
|
||||
packet[1] = (uint8_t)(id & 0xFF);
|
||||
packet[2] = (uint8_t)(DNS_FLAGS_RD >> 8); // Flags high: RD=1
|
||||
packet[3] = (uint8_t)(DNS_FLAGS_RD & 0xFF); // Flags low
|
||||
packet[4] = 0; packet[5] = 1; // QDCOUNT = 1
|
||||
packet[6] = 0; packet[7] = 0; // ANCOUNT = 0
|
||||
packet[8] = 0; packet[9] = 0; // NSCOUNT = 0
|
||||
packet[10] = 0; packet[11] = 0; // ARCOUNT = 0
|
||||
|
||||
// Question section
|
||||
int nameLen = EncodeName(hostname, packet + 12, maxLen - 12 - 4);
|
||||
if (nameLen == 0) return 0;
|
||||
|
||||
int pos = 12 + nameLen;
|
||||
if (pos + 4 > maxLen) return 0;
|
||||
|
||||
// QTYPE = A (1)
|
||||
packet[pos++] = 0;
|
||||
packet[pos++] = DNS_QTYPE_A;
|
||||
// QCLASS = IN (1)
|
||||
packet[pos++] = 0;
|
||||
packet[pos++] = DNS_QCLASS_IN;
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
// ---- DNS response parsing ----
|
||||
|
||||
// Skip over a DNS name in the packet (handles compression pointers).
|
||||
// Returns the new offset, or -1 on error.
|
||||
static int SkipName(const uint8_t* packet, int packetLen, int offset) {
|
||||
int maxJumps = 32; // prevent infinite loops
|
||||
bool jumped = false;
|
||||
int returnOffset = -1;
|
||||
|
||||
while (offset < packetLen && maxJumps > 0) {
|
||||
uint8_t len = packet[offset];
|
||||
|
||||
if (len == 0) {
|
||||
// End of name
|
||||
offset++;
|
||||
return jumped ? returnOffset : offset;
|
||||
}
|
||||
|
||||
if ((len & 0xC0) == 0xC0) {
|
||||
// Compression pointer
|
||||
if (offset + 1 >= packetLen) return -1;
|
||||
if (!jumped) returnOffset = offset + 2;
|
||||
offset = ((len & 0x3F) << 8) | packet[offset + 1];
|
||||
jumped = true;
|
||||
maxJumps--;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Regular label
|
||||
offset += 1 + len;
|
||||
maxJumps--;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct DnsAnswer {
|
||||
uint32_t ip;
|
||||
uint32_t ttl;
|
||||
bool found;
|
||||
};
|
||||
|
||||
// Parse a DNS response and extract the first A record.
|
||||
static DnsAnswer ParseResponse(uint16_t expectedId, const uint8_t* packet, int packetLen) {
|
||||
DnsAnswer result = {0, 0, false};
|
||||
|
||||
if (packetLen < 12) return result;
|
||||
|
||||
// Check ID
|
||||
uint16_t id = ((uint16_t)packet[0] << 8) | packet[1];
|
||||
if (id != expectedId) return result;
|
||||
|
||||
// Check QR bit (must be response)
|
||||
if (!(packet[2] & 0x80)) return result;
|
||||
|
||||
// Check RCODE (must be 0 = no error)
|
||||
uint8_t rcode = packet[3] & 0x0F;
|
||||
if (rcode != 0) return result;
|
||||
|
||||
uint16_t qdcount = ((uint16_t)packet[4] << 8) | packet[5];
|
||||
uint16_t ancount = ((uint16_t)packet[6] << 8) | packet[7];
|
||||
|
||||
// Skip question section
|
||||
int offset = 12;
|
||||
for (uint16_t i = 0; i < qdcount; i++) {
|
||||
offset = SkipName(packet, packetLen, offset);
|
||||
if (offset < 0) return result;
|
||||
offset += 4; // QTYPE + QCLASS
|
||||
if (offset > packetLen) return result;
|
||||
}
|
||||
|
||||
// Parse answers
|
||||
for (uint16_t i = 0; i < ancount; i++) {
|
||||
offset = SkipName(packet, packetLen, offset);
|
||||
if (offset < 0 || offset + 10 > packetLen) return result;
|
||||
|
||||
uint16_t atype = ((uint16_t)packet[offset] << 8) | packet[offset + 1];
|
||||
// uint16_t aclass = ((uint16_t)packet[offset + 2] << 8) | packet[offset + 3];
|
||||
uint32_t attl = ((uint32_t)packet[offset + 4] << 24) |
|
||||
((uint32_t)packet[offset + 5] << 16) |
|
||||
((uint32_t)packet[offset + 6] << 8) |
|
||||
((uint32_t)packet[offset + 7]);
|
||||
uint16_t rdlen = ((uint16_t)packet[offset + 8] << 8) | packet[offset + 9];
|
||||
offset += 10;
|
||||
|
||||
if (offset + rdlen > packetLen) return result;
|
||||
|
||||
if (atype == DNS_QTYPE_A && rdlen == 4) {
|
||||
// A record: 4-byte IPv4 address (already in network byte order)
|
||||
result.ip = ((uint32_t)packet[offset])
|
||||
| ((uint32_t)packet[offset + 1] << 8)
|
||||
| ((uint32_t)packet[offset + 2] << 16)
|
||||
| ((uint32_t)packet[offset + 3] << 24);
|
||||
result.ttl = attl;
|
||||
result.found = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
offset += rdlen;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ---- Resolve state (shared with UDP callback) ----
|
||||
|
||||
static volatile bool g_gotResponse = false;
|
||||
static volatile uint16_t g_currentId = 0;
|
||||
static uint8_t g_responseBuffer[512];
|
||||
static volatile int g_responseLen = 0;
|
||||
|
||||
static void DnsRecvCallback(uint32_t srcIp, uint16_t srcPort,
|
||||
uint16_t dstPort,
|
||||
const uint8_t* data, uint16_t length) {
|
||||
(void)srcIp;
|
||||
(void)srcPort;
|
||||
(void)dstPort;
|
||||
|
||||
if (g_gotResponse) return; // Already got a response
|
||||
if (length > sizeof(g_responseBuffer)) length = sizeof(g_responseBuffer);
|
||||
|
||||
memcpy(g_responseBuffer, data, length);
|
||||
g_responseLen = length;
|
||||
g_gotResponse = true;
|
||||
}
|
||||
|
||||
// ---- Simple PRNG for transaction IDs ----
|
||||
|
||||
static uint16_t g_nextId = 0x4E53; // "NS"
|
||||
|
||||
static uint16_t NextId() {
|
||||
g_nextId = g_nextId * 25173 + 13849;
|
||||
return g_nextId;
|
||||
}
|
||||
|
||||
// ---- Check if string is already an IP address ----
|
||||
|
||||
static bool IsIpAddress(const char* s) {
|
||||
int dotCount = 0;
|
||||
bool hasDigit = false;
|
||||
for (int i = 0; s[i]; i++) {
|
||||
if (s[i] >= '0' && s[i] <= '9') {
|
||||
hasDigit = true;
|
||||
} else if (s[i] == '.') {
|
||||
if (!hasDigit) return false;
|
||||
dotCount++;
|
||||
hasDigit = false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return hasDigit && dotCount == 3;
|
||||
}
|
||||
|
||||
// ---- Public API ----
|
||||
|
||||
uint32_t Resolve(const char* hostname, uint32_t timeoutMs) {
|
||||
if (hostname == nullptr || hostname[0] == '\0') return 0;
|
||||
|
||||
// Don't try to resolve IP addresses
|
||||
if (IsIpAddress(hostname)) return 0;
|
||||
|
||||
// Check cache first
|
||||
uint32_t cached = CacheLookup(hostname);
|
||||
if (cached != 0) return cached;
|
||||
|
||||
// Check DNS server is configured
|
||||
uint32_t dnsServer = Net::GetDnsServer();
|
||||
if (dnsServer == 0) return 0;
|
||||
|
||||
// Pick a local port for receiving the response (ephemeral range)
|
||||
uint16_t localPort = 10000 + (NextId() % 50000);
|
||||
uint16_t txId = NextId();
|
||||
|
||||
// Build DNS query
|
||||
uint8_t queryPacket[512];
|
||||
int queryLen = BuildQuery(txId, hostname, queryPacket, sizeof(queryPacket));
|
||||
if (queryLen == 0) return 0;
|
||||
|
||||
// Reset response state
|
||||
g_gotResponse = false;
|
||||
g_responseLen = 0;
|
||||
g_currentId = txId;
|
||||
|
||||
// Bind our receive port
|
||||
if (!Net::Udp::Bind(localPort, DnsRecvCallback)) {
|
||||
// Port might be in use, try another
|
||||
localPort = 10000 + (NextId() % 50000);
|
||||
if (!Net::Udp::Bind(localPort, DnsRecvCallback)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Send the query to DNS server port 53
|
||||
bool sent = Net::Udp::Send(dnsServer, localPort, DNS_PORT, queryPacket, (uint16_t)queryLen);
|
||||
if (!sent) {
|
||||
Net::Udp::Unbind(localPort);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Wait for response with timeout
|
||||
uint64_t start = Timekeeping::GetMilliseconds();
|
||||
while (!g_gotResponse) {
|
||||
if (Timekeeping::GetMilliseconds() - start >= timeoutMs) {
|
||||
Net::Udp::Unbind(localPort);
|
||||
return 0;
|
||||
}
|
||||
Sched::Schedule();
|
||||
}
|
||||
|
||||
// Unbind the port
|
||||
Net::Udp::Unbind(localPort);
|
||||
|
||||
// Parse the response
|
||||
DnsAnswer answer = ParseResponse(txId, g_responseBuffer, g_responseLen);
|
||||
if (!answer.found) return 0;
|
||||
|
||||
// Cache the result
|
||||
CacheStore(hostname, answer.ip, answer.ttl);
|
||||
|
||||
return answer.ip;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user