diff --git a/include/net/dhcp.h b/include/net/dhcp.h index cca48b3..5f2c41b 100644 --- a/include/net/dhcp.h +++ b/include/net/dhcp.h @@ -5,6 +5,23 @@ #include void dhcp_init(void); + +// Run a full DHCP handshake (DISCOVER/OFFER/REQUEST/ACK) and, on success, apply +// the offered IP, mask, gateway and DNS to the primary interface. Returns 0 on +// a granted lease, -1 on timeout (the static configuration is left in place). int dhcp_request_lease(void); +// Last-lease details for status reporting (valid after a successful lease). +typedef struct { + uint32_t ip; + uint32_t mask; + uint32_t gateway; + uint32_t dns; + uint32_t server; + uint32_t lease_secs; + bool bound; +} dhcp_lease_t; + +const dhcp_lease_t* dhcp_get_lease(void); + #endif // _NET_DHCP_H diff --git a/include/net/socket.h b/include/net/socket.h index a0dcd96..4080db2 100644 --- a/include/net/socket.h +++ b/include/net/socket.h @@ -41,6 +41,18 @@ int sys_bind(int sockfd, const struct sockaddr_in* addr, size_t addrlen); int sys_connect(int sockfd, const struct sockaddr_in* addr, size_t addrlen); ssize_t sys_send(int sockfd, const void* buf, size_t len, int flags); ssize_t sys_recv(int sockfd, void* buf, size_t len, int flags); + +// Address-carrying variants for connectionless (SOCK_DGRAM) use. +ssize_t sys_sendto(int sockfd, const void* buf, size_t len, int flags, + const struct sockaddr_in* dest, size_t addrlen); +ssize_t sys_recvfrom(int sockfd, void* buf, size_t len, int flags, + struct sockaddr_in* src, size_t* addrlen); + int sys_close_socket(int sockfd); +// Introspection for netstat / sockstat. +int socket_get_count(void); +const socket_t* socket_get(int idx); +void socket_get_stats(uint64_t* tx_dgrams, uint64_t* rx_dgrams, uint64_t* drops); + #endif // _NET_SOCKET_H diff --git a/include/net/udp.h b/include/net/udp.h index d4518db..a6e593f 100644 --- a/include/net/udp.h +++ b/include/net/udp.h @@ -3,8 +3,37 @@ #include +// A bound UDP port dispatches every matching datagram to this callback. It runs +// in soft-IRQ / receive context, so handlers must be quick and non-blocking: +// stash the datagram and set a completion flag, then process it from a thread. +typedef void (*udp_handler_t)(uint32_t src_ip, uint16_t src_port, + uint16_t dst_port, const uint8_t* data, + uint16_t len, void* ctx); + void udp_init(void); + +// Demultiplex an inbound IPv4 payload (protocol 17) to a bound port. void udp_receive(const uint8_t* packet, uint16_t length, uint32_t src_ip); -void udp_send(uint32_t dst_ip, uint16_t src_port, uint16_t dst_port, const void* data, uint16_t len); + +// Emit a datagram to dst_ip:dst_port from src_port (checksummed). +void udp_send(uint32_t dst_ip, uint16_t src_port, uint16_t dst_port, + const void* data, uint16_t len); + +// Emit a datagram to the limited broadcast address (255.255.255.255) from +// source IP 0.0.0.0 — used before the interface has an address (DHCP). +void udp_send_broadcast(uint16_t src_port, uint16_t dst_port, + const void* data, uint16_t len); + +// Register/unregister a receive handler for a local port. udp_bind returns 0 on +// success, -1 if the port is taken or the table is full. +int udp_bind(uint16_t port, udp_handler_t handler, void* ctx); +void udp_unbind(uint16_t port); + +// Allocate an unused ephemeral port in the 49152-65535 range. +uint16_t udp_alloc_ephemeral_port(void); + +// Counters: datagrams received, transmitted, and dropped (no listener / bad). +void udp_get_stats(uint64_t* rx, uint64_t* tx, uint64_t* drop); +int udp_get_bindings(uint16_t* ports_out, int max); #endif // _NET_UDP_H diff --git a/net/core/net.c b/net/core/net.c index cb26191..819df79 100644 --- a/net/core/net.c +++ b/net/core/net.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -247,6 +248,8 @@ void net_receive(const uint8_t* packet, uint16_t length) { net_handle_icmp(ip->src_ip, payload + ihl, payload_len - ihl); } else if (ip->protocol == IP_PROTO_TCP) { tcp_receive(payload + ihl, payload_len - ihl, ip->src_ip); + } else if (ip->protocol == IP_PROTO_UDP) { + udp_receive(payload + ihl, payload_len - ihl, ip->src_ip); } } } diff --git a/net/core/socket.c b/net/core/socket.c index 6a47457..85decec 100644 --- a/net/core/socket.c +++ b/net/core/socket.c @@ -1,24 +1,91 @@ +// BSD-style socket layer for SUB-OS. +// +// Implements connectionless (SOCK_DGRAM) sockets on top of the UDP transport. +// Binding a datagram socket registers a UDP handler that appends inbound +// datagrams to the socket's fixed-depth receive ring; sys_recv/sys_recvfrom pop +// the oldest datagram. Stream (SOCK_STREAM) sockets are accepted and tracked so +// the API is uniform, but their data path is served by the in-kernel TCP engine. #include +#include +#include #include #include -#define MAX_SOCKETS 32 -static socket_t socket_table[MAX_SOCKETS]; +#define MAX_SOCKETS 32 +#define RX_RING_DEPTH 8 +#define DGRAM_MAX 1472 // 1500 MTU - 20 IP - 8 UDP + +typedef struct { + uint32_t src_ip; + uint16_t src_port; + uint16_t len; + uint8_t data[DGRAM_MAX]; +} dgram_t; + +// Per-socket receive ring, kept parallel to the public socket_t so the exported +// struct stays small. +typedef struct { + dgram_t ring[RX_RING_DEPTH]; + int head; // next slot to read + int count; // queued datagrams + bool bound; // a UDP binding is installed for local_port +} sock_priv_t; + +static socket_t socket_table[MAX_SOCKETS]; +static sock_priv_t socket_priv[MAX_SOCKETS]; + +static volatile uint64_t stat_tx = 0; +static volatile uint64_t stat_rx = 0; +static volatile uint64_t stat_drop = 0; void socket_subsystem_init(void) { memset(socket_table, 0, sizeof(socket_table)); + memset(socket_priv, 0, sizeof(socket_priv)); + stat_tx = stat_rx = stat_drop = 0; printk(KERN_INFO "NET: BSD Sockets API Layer initialized (AF_INET support)\n"); } +// Runs in UDP receive context: enqueue the datagram, dropping the oldest if the +// ring is full so a stalled reader never wedges delivery. +static void socket_udp_cb(uint32_t src_ip, uint16_t src_port, uint16_t dst_port, + const uint8_t* data, uint16_t len, void* ctx) { + (void)dst_port; + sock_priv_t* pv = (sock_priv_t*)ctx; + if (!pv) return; + if (len > DGRAM_MAX) len = DGRAM_MAX; + + int slot; + if (pv->count == RX_RING_DEPTH) { + // Overwrite the oldest. + pv->head = (pv->head + 1) % RX_RING_DEPTH; + slot = (pv->head + pv->count - 1) % RX_RING_DEPTH; + stat_drop++; + } else { + slot = (pv->head + pv->count) % RX_RING_DEPTH; + pv->count++; + } + + dgram_t* d = &pv->ring[slot]; + d->src_ip = src_ip; + d->src_port = src_port; + d->len = len; + if (data && len) memcpy(d->data, data, len); + stat_rx++; +} + int sys_socket(int domain, int type, int protocol) { if (domain != AF_INET) return -1; + if (type != SOCK_DGRAM && type != SOCK_STREAM && type != SOCK_RAW) return -1; for (int i = 0; i < MAX_SOCKETS; i++) { if (!socket_table[i].in_use) { + memset(&socket_table[i], 0, sizeof(socket_t)); + memset(&socket_priv[i], 0, sizeof(sock_priv_t)); socket_table[i].in_use = true; socket_table[i].domain = domain; socket_table[i].type = type; - socket_table[i].protocol = protocol; + socket_table[i].protocol = protocol ? protocol : + (type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP); socket_table[i].state = 0; return i; } @@ -26,37 +93,122 @@ int sys_socket(int domain, int type, int protocol) { return -1; } +static bool valid_fd(int fd) { + return fd >= 0 && fd < MAX_SOCKETS && socket_table[fd].in_use; +} + +// Install the UDP binding for a datagram socket's local port (idempotent). +static int dgram_ensure_bound(int fd) { + socket_t* s = &socket_table[fd]; + sock_priv_t* pv = &socket_priv[fd]; + if (pv->bound) return 0; + if (s->local_port == 0) { + s->local_port = udp_alloc_ephemeral_port(); + if (s->local_port == 0) return -1; + } + if (udp_bind(s->local_port, socket_udp_cb, pv) != 0) return -1; + pv->bound = true; + return 0; +} + int sys_bind(int sockfd, const struct sockaddr_in* addr, size_t addrlen) { (void)addrlen; - if (sockfd < 0 || sockfd >= MAX_SOCKETS || !socket_table[sockfd].in_use || !addr) return -1; - socket_table[sockfd].local_ip = addr->sin_addr; - socket_table[sockfd].local_port = addr->sin_port; + if (!valid_fd(sockfd) || !addr) return -1; + socket_t* s = &socket_table[sockfd]; + s->local_ip = addr->sin_addr; + s->local_port = ntohs(addr->sin_port); + if (s->type == SOCK_DGRAM) return dgram_ensure_bound(sockfd); return 0; } int sys_connect(int sockfd, const struct sockaddr_in* addr, size_t addrlen) { (void)addrlen; - if (sockfd < 0 || sockfd >= MAX_SOCKETS || !socket_table[sockfd].in_use || !addr) return -1; - socket_table[sockfd].remote_ip = addr->sin_addr; - socket_table[sockfd].remote_port = addr->sin_port; - socket_table[sockfd].state = 1; // Connected + if (!valid_fd(sockfd) || !addr) return -1; + socket_t* s = &socket_table[sockfd]; + s->remote_ip = addr->sin_addr; + s->remote_port = ntohs(addr->sin_port); + s->state = 1; // "connected" (address memorised for send/recv) + if (s->type == SOCK_DGRAM) return dgram_ensure_bound(sockfd); return 0; } -ssize_t sys_send(int sockfd, const void* buf, size_t len, int flags) { - (void)flags; - if (sockfd < 0 || sockfd >= MAX_SOCKETS || !socket_table[sockfd].in_use || !buf) return -1; +ssize_t sys_sendto(int sockfd, const void* buf, size_t len, int flags, + const struct sockaddr_in* dest, size_t addrlen) { + (void)flags; (void)addrlen; + if (!valid_fd(sockfd) || !buf) return -1; + socket_t* s = &socket_table[sockfd]; + if (s->type != SOCK_DGRAM) return -1; + if (len > DGRAM_MAX) len = DGRAM_MAX; + + uint32_t dip; + uint16_t dport; + if (dest) { dip = dest->sin_addr; dport = ntohs(dest->sin_port); } + else if (s->state == 1) { dip = s->remote_ip; dport = s->remote_port; } + else return -1; + + if (dgram_ensure_bound(sockfd) != 0) return -1; + udp_send(dip, s->local_port, dport, buf, (uint16_t)len); + stat_tx++; return (ssize_t)len; } -ssize_t sys_recv(int sockfd, void* buf, size_t len, int flags) { +ssize_t sys_send(int sockfd, const void* buf, size_t len, int flags) { + return sys_sendto(sockfd, buf, len, flags, NULL, 0); +} + +ssize_t sys_recvfrom(int sockfd, void* buf, size_t len, int flags, + struct sockaddr_in* src, size_t* addrlen) { (void)flags; - if (sockfd < 0 || sockfd >= MAX_SOCKETS || !socket_table[sockfd].in_use || !buf) return -1; - return 0; + if (!valid_fd(sockfd) || !buf) return -1; + socket_t* s = &socket_table[sockfd]; + if (s->type != SOCK_DGRAM) return 0; + + sock_priv_t* pv = &socket_priv[sockfd]; + if (pv->count == 0) return 0; // non-blocking: nothing queued + + dgram_t* d = &pv->ring[pv->head]; + size_t n = d->len < len ? d->len : len; + memcpy(buf, d->data, n); + + if (src) { + src->sin_family = AF_INET; + src->sin_addr = d->src_ip; + src->sin_port = htons(d->src_port); + if (addrlen) *addrlen = sizeof(struct sockaddr_in); + } + + pv->head = (pv->head + 1) % RX_RING_DEPTH; + pv->count--; + return (ssize_t)n; +} + +ssize_t sys_recv(int sockfd, void* buf, size_t len, int flags) { + return sys_recvfrom(sockfd, buf, len, flags, NULL, NULL); } int sys_close_socket(int sockfd) { - if (sockfd < 0 || sockfd >= MAX_SOCKETS || !socket_table[sockfd].in_use) return -1; - memset(&socket_table[sockfd], 0, sizeof(socket_t)); + if (!valid_fd(sockfd)) return -1; + socket_t* s = &socket_table[sockfd]; + sock_priv_t* pv = &socket_priv[sockfd]; + if (pv->bound && s->local_port) udp_unbind(s->local_port); + memset(s, 0, sizeof(socket_t)); + memset(pv, 0, sizeof(sock_priv_t)); return 0; } + +int socket_get_count(void) { + int n = 0; + for (int i = 0; i < MAX_SOCKETS; i++) if (socket_table[i].in_use) n++; + return n; +} + +const socket_t* socket_get(int idx) { + if (idx < 0 || idx >= MAX_SOCKETS || !socket_table[idx].in_use) return NULL; + return &socket_table[idx]; +} + +void socket_get_stats(uint64_t* tx_dgrams, uint64_t* rx_dgrams, uint64_t* drops) { + if (tx_dgrams) *tx_dgrams = stat_tx; + if (rx_dgrams) *rx_dgrams = stat_rx; + if (drops) *drops = stat_drop; +} diff --git a/net/ipv4/dhcp.c b/net/ipv4/dhcp.c index a286580..cc1397e 100644 --- a/net/ipv4/dhcp.c +++ b/net/ipv4/dhcp.c @@ -1,13 +1,220 @@ +// DHCP (Dynamic Host Configuration Protocol) client for SUB-OS. +// +// Performs the classic four-way handshake over UDP: broadcast DISCOVER, receive +// OFFER, broadcast REQUEST, receive ACK. On ACK the offered IP address, subnet +// mask, gateway and DNS server are applied to the primary interface. Each phase +// is bounded by a timeout so an absent server degrades to the static fallback +// configuration rather than stalling boot. #include #include +#include +#include +#include +#include #include +#include + +#define DHCP_SERVER_PORT 67 +#define DHCP_CLIENT_PORT 68 +#define DHCP_MAGIC 0x63825363u +#define DHCP_TIMEOUT_MS 2500 + +// BOOTP message ops and DHCP message types (option 53). +#define BOOTREQUEST 1 +#define BOOTREPLY 2 +#define DHCPDISCOVER 1 +#define DHCPOFFER 2 +#define DHCPREQUEST 3 +#define DHCPACK 5 +#define DHCPNAK 6 + +typedef struct { + uint8_t op; + uint8_t htype; + uint8_t hlen; + uint8_t hops; + uint32_t xid; + uint16_t secs; + uint16_t flags; + uint32_t ciaddr; + uint32_t yiaddr; + uint32_t siaddr; + uint32_t giaddr; + uint8_t chaddr[16]; + uint8_t sname[64]; + uint8_t file[128]; + uint32_t cookie; + uint8_t options[312]; +} __attribute__((packed)) dhcp_packet_t; + +static dhcp_lease_t g_lease; + +// Filled from UDP receive context on OFFER/ACK. +static volatile bool reply_ready = false; +static volatile uint8_t reply_type = 0; +static volatile uint32_t reply_xid = 0; +static volatile uint32_t off_yiaddr = 0; +static volatile uint32_t off_mask = 0; +static volatile uint32_t off_router = 0; +static volatile uint32_t off_dns = 0; +static volatile uint32_t off_server = 0; +static volatile uint32_t off_lease = 0; void dhcp_init(void) { + memset(&g_lease, 0, sizeof(g_lease)); + reply_ready = false; printk(KERN_INFO "DHCP: Dynamic Host Configuration Protocol client ready\n"); } +const dhcp_lease_t* dhcp_get_lease(void) { return &g_lease; } + +// Append a TLV option; returns the new write offset. +static int opt_put(uint8_t* opts, int o, uint8_t code, uint8_t len, const void* val) { + opts[o++] = code; + opts[o++] = len; + if (len && val) { memcpy(&opts[o], val, len); o += len; } + return o; +} + +static void dhcp_udp_cb(uint32_t src_ip, uint16_t src_port, uint16_t dst_port, + const uint8_t* data, uint16_t len, void* ctx) { + (void)src_ip; (void)src_port; (void)dst_port; (void)ctx; + if (len < 240) return; + const dhcp_packet_t* p = (const dhcp_packet_t*)data; + if (p->op != BOOTREPLY) return; + if (ntohl(p->cookie) != DHCP_MAGIC) return; + + uint32_t mask = 0, router = 0, dns = 0, server = 0, lease = 0; + uint8_t mtype = 0; + + int optlen = len - (int)((const uint8_t*)p->options - (const uint8_t*)p); + int i = 0; + while (i < optlen) { + uint8_t code = p->options[i++]; + if (code == 0xFF) break; // end + if (code == 0x00) continue; // pad + if (i >= optlen) break; + uint8_t l = p->options[i++]; + if (i + l > optlen) break; + const uint8_t* v = &p->options[i]; + switch (code) { + case 53: mtype = v[0]; break; + case 1: if (l >= 4) memcpy(&mask, v, 4); break; + case 3: if (l >= 4) memcpy(&router, v, 4); break; + case 6: if (l >= 4) memcpy(&dns, v, 4); break; + case 54: if (l >= 4) memcpy(&server, v, 4); break; + case 51: if (l >= 4) { uint32_t t; memcpy(&t, v, 4); lease = ntohl(t); } break; + default: break; + } + i += l; + } + + reply_type = mtype; + reply_xid = ntohl(p->xid); + off_yiaddr = p->yiaddr; + off_mask = mask; + off_router = router; + off_dns = dns; + off_server = server; + off_lease = lease; + reply_ready = true; +} + +// Build a DISCOVER or REQUEST and broadcast it. requested_ip/server are set for +// REQUEST (0 for DISCOVER). +static void dhcp_send(uint8_t msgtype, uint32_t xid, const uint8_t* mac, + uint32_t requested_ip, uint32_t server_id) { + dhcp_packet_t* p = (dhcp_packet_t*)kzalloc(sizeof(dhcp_packet_t)); + if (!p) return; + + p->op = BOOTREQUEST; + p->htype = 1; // Ethernet + p->hlen = 6; + p->xid = htonl(xid); + p->flags = htons(0x8000); // ask the server to broadcast its reply + memcpy(p->chaddr, mac, 6); + p->cookie = htonl(DHCP_MAGIC); + + int o = 0; + o = opt_put(p->options, o, 53, 1, &msgtype); + if (msgtype == DHCPREQUEST) { + if (requested_ip) o = opt_put(p->options, o, 50, 4, &requested_ip); + if (server_id) o = opt_put(p->options, o, 54, 4, &server_id); + } + uint8_t plist[4] = {1, 3, 6, 15}; // mask, router, dns, domain + o = opt_put(p->options, o, 55, sizeof(plist), plist); + p->options[o++] = 0xFF; // end + + int fixed = (int)((uint8_t*)p->options - (uint8_t*)p); + udp_send_broadcast(DHCP_CLIENT_PORT, DHCP_SERVER_PORT, p, (uint16_t)(fixed + o)); + kfree(p); +} + +static bool wait_reply(uint32_t xid, uint8_t want_type) { + uint64_t t0 = pit_get_ticks(); + uint64_t wait_ticks = DHCP_TIMEOUT_MS / 10; + while (pit_get_ticks() - t0 < wait_ticks) { + if (reply_ready && reply_xid == xid && reply_type == want_type) return true; + arch_halt(); + } + return false; +} + int dhcp_request_lease(void) { + net_if_t* nif = net_get_primary_if(); + if (!nif) return -1; + printk(KERN_INFO "DHCP: Discovering network configuration via broadcast...\n"); - printk(KERN_INFO "DHCP: Lease granted: IP 10.0.2.15, Mask 255.255.255.0, GW 10.0.2.2, DNS 10.0.2.3\n"); - return 0; + + if (udp_bind(DHCP_CLIENT_PORT, dhcp_udp_cb, NULL) != 0) { + printk(ANSI_YELLOW "DHCP: client port 68 unavailable\n" ANSI_RESET); + return -1; + } + + uint32_t xid = (uint32_t)(0x53554200u ^ pit_get_ticks()); + int rc = -1; + + // DISCOVER -> OFFER + reply_ready = false; + dhcp_send(DHCPDISCOVER, xid, nif->mac, 0, 0); + if (wait_reply(xid, DHCPOFFER)) { + uint32_t offered = off_yiaddr; + uint32_t server = off_server; + char ips[16]; ip_to_str(offered, ips); + printk(KERN_INFO "DHCP: OFFER received: %s\n", ips); + + // REQUEST -> ACK + reply_ready = false; + dhcp_send(DHCPREQUEST, xid, nif->mac, offered, server); + if (wait_reply(xid, DHCPACK)) { + nif->ip = off_yiaddr; + if (off_mask) nif->subnet = off_mask; + if (off_router) nif->gateway = off_router; + if (off_dns) nif->dns = off_dns; + + g_lease.ip = nif->ip; + g_lease.mask = nif->subnet; + g_lease.gateway = nif->gateway; + g_lease.dns = nif->dns; + g_lease.server = off_server; + g_lease.lease_secs = off_lease ? off_lease : 86400; + g_lease.bound = true; + + char a[16], m[16], gw[16], d[16]; + ip_to_str(nif->ip, a); ip_to_str(nif->subnet, m); + ip_to_str(nif->gateway, gw); ip_to_str(nif->dns, d); + printk(ANSI_BRIGHT_GREEN + "DHCP: Lease granted: IP %s, Mask %s, GW %s, DNS %s (lease %us)\n" + ANSI_RESET, a, m, gw, d, g_lease.lease_secs); + rc = 0; + } + } + + udp_unbind(DHCP_CLIENT_PORT); + + if (rc != 0) { + char a[16]; ip_to_str(nif->ip, a); + printk(ANSI_YELLOW "DHCP: no response; keeping static config %s\n" ANSI_RESET, a); + } + return rc; } diff --git a/net/ipv4/dns.c b/net/ipv4/dns.c index c03cd87..79be4af 100644 --- a/net/ipv4/dns.c +++ b/net/ipv4/dns.c @@ -1,17 +1,183 @@ +// DNS (Domain Name System) stub resolver for SUB-OS. +// +// Resolves A records by sending a real DNS query over UDP/53 to the interface's +// configured nameserver and parsing the response. Results are cached (with TTL) +// via the in-kernel DNS cache; the resolver first consults the cache, then a +// small built-in hosts table, and only then hits the wire. Every network wait +// is bounded so a missing or silent server degrades to a timeout, never a hang. #include #include +#include +#include +#include #include +#include #include +#include + +#define DNS_PORT 53 +#define DNS_TIMEOUT_MS 2000 +#define DNS_MAX_MSG 512 + +typedef struct { + uint16_t id; + uint16_t flags; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; +} __attribute__((packed)) dns_header_t; + +// Filled from UDP receive context when a matching reply arrives. +static volatile bool dns_reply_ready = false; +static volatile uint16_t dns_reply_id = 0; +static volatile uint32_t dns_reply_ip = 0; +static volatile uint32_t dns_reply_ttl = 0; void dns_init(void) { + dns_reply_ready = false; printk(KERN_INFO "DNS: Domain Name System Resolver client ready (Nameserver: 10.0.2.3)\n"); } +// Encode "www.example.com" as DNS labels: 3www7example3com0. +static int dns_encode_name(const char* host, uint8_t* out, int max) { + int o = 0; + const char* seg = host; + while (*seg) { + const char* dot = seg; + while (*dot && *dot != '.') dot++; + int seglen = (int)(dot - seg); + if (seglen <= 0 || seglen > 63 || o + seglen + 1 >= max) return -1; + out[o++] = (uint8_t)seglen; + for (int i = 0; i < seglen; i++) out[o++] = (uint8_t)seg[i]; + seg = (*dot == '.') ? dot + 1 : dot; + } + if (o + 1 >= max) return -1; + out[o++] = 0; // root label + return o; +} + +// Skip a (possibly compressed) name in the answer section, returning the offset +// just past it. Compression pointers (0xC0) end the name in two bytes. +static int dns_skip_name(const uint8_t* msg, int off, int len) { + while (off < len) { + uint8_t b = msg[off]; + if (b == 0) return off + 1; + if ((b & 0xC0) == 0xC0) return off + 2; + off += b + 1; + } + return off; +} + +static void dns_udp_cb(uint32_t src_ip, uint16_t src_port, uint16_t dst_port, + const uint8_t* data, uint16_t len, void* ctx) { + (void)src_ip; (void)src_port; (void)dst_port; (void)ctx; + if (len < sizeof(dns_header_t)) return; + + const dns_header_t* h = (const dns_header_t*)data; + uint16_t id = ntohs(h->id); + uint16_t ancount = ntohs(h->ancount); + uint16_t qdcount = ntohs(h->qdcount); + if (ancount == 0) { dns_reply_id = id; dns_reply_ready = true; return; } + + int off = sizeof(dns_header_t); + // Skip the echoed question(s): name + qtype(2) + qclass(2). + for (uint16_t q = 0; q < qdcount; q++) { + off = dns_skip_name(data, off, len); + off += 4; + } + + for (uint16_t a = 0; a < ancount && off + 12 <= len; a++) { + off = dns_skip_name(data, off, len); + if (off + 10 > len) break; + uint16_t type = ntohs(*(const uint16_t*)(data + off)); + uint32_t ttl = ntohl(*(const uint32_t*)(data + off + 4)); + uint16_t rdlen = ntohs(*(const uint16_t*)(data + off + 8)); + off += 10; + if (off + rdlen > len) break; + if (type == 1 && rdlen == 4) { // A record + uint32_t ip; + memcpy(&ip, data + off, 4); // already network byte order == our storage + dns_reply_ip = ip; + dns_reply_ttl = ttl ? ttl : 300; + dns_reply_id = id; + dns_reply_ready = true; + return; + } + off += rdlen; + } + dns_reply_id = id; + dns_reply_ready = true; // answered, but no A record found +} + +// Built-in hosts fallback for when the wire query yields nothing. +static uint32_t dns_static_host(const char* host) { + if (strcmp(host, "localhost") == 0) return ip_parse("127.0.0.1"); + if (strcmp(host, "gateway") == 0) return ip_parse("10.0.2.2"); + if (strcmp(host, "dns.google") == 0) return 0x08080808; + if (strcmp(host, "google.com") == 0) return ip_parse("142.250.190.46"); + if (strcmp(host, "github.com") == 0) return ip_parse("140.82.121.4"); + return 0; +} + uint32_t dns_resolve(const char* hostname) { - if (!hostname) return 0; - if (strcmp(hostname, "localhost") == 0) return ip_parse("127.0.0.1"); - if (strcmp(hostname, "gateway") == 0) return ip_parse("10.0.2.2"); - if (strcmp(hostname, "google.com") == 0) return ip_parse("142.250.190.46"); - if (strcmp(hostname, "github.com") == 0) return ip_parse("140.82.121.4"); - return ip_parse("10.0.2.15"); + if (!hostname || !*hostname) return 0; + + // Dotted-quad literal: no lookup needed. + { + bool numeric = true; + for (const char* p = hostname; *p; p++) { + if (!((*p >= '0' && *p <= '9') || *p == '.')) { numeric = false; break; } + } + if (numeric) return ip_parse(hostname); + } + + // 1) Cache. + uint32_t cached; + if (dns_cache_lookup(hostname, &cached)) return cached; + + // 2) Wire query. + net_if_t* nif = net_get_primary_if(); + uint32_t server = (nif && nif->dns) ? nif->dns : ip_parse("10.0.2.3"); + uint16_t sport = udp_alloc_ephemeral_port(); + if (sport && udp_bind(sport, dns_udp_cb, NULL) == 0) { + uint8_t query[DNS_MAX_MSG]; + dns_header_t* h = (dns_header_t*)query; + uint16_t qid = (uint16_t)(0x5300u ^ (pit_get_ticks() & 0xFF)); + h->id = htons(qid); + h->flags = htons(0x0100); // standard query, recursion desired + h->qdcount = htons(1); + h->ancount = 0; h->nscount = 0; h->arcount = 0; + + int off = sizeof(dns_header_t); + int nl = dns_encode_name(hostname, query + off, DNS_MAX_MSG - off - 4); + if (nl > 0) { + off += nl; + *(uint16_t*)(query + off) = htons(1); off += 2; // QTYPE A + *(uint16_t*)(query + off) = htons(1); off += 2; // QCLASS IN + + dns_reply_ready = false; + dns_reply_ip = 0; + uint64_t t0 = pit_get_ticks(); + udp_send(server, sport, DNS_PORT, query, (uint16_t)off); + + uint64_t wait_ticks = DNS_TIMEOUT_MS / 10; + while (!dns_reply_ready && (pit_get_ticks() - t0 < wait_ticks)) { + arch_halt(); + } + + if (dns_reply_ready && dns_reply_id == qid && dns_reply_ip != 0) { + uint32_t ip = dns_reply_ip; + udp_unbind(sport); + dns_cache_insert(hostname, ip, dns_reply_ttl); + return ip; + } + } + udp_unbind(sport); + } + + // 3) Static hosts fallback. + uint32_t st = dns_static_host(hostname); + if (st) { dns_cache_insert(hostname, st, 300); return st; } + return 0; } diff --git a/net/ipv4/dns_cache.c b/net/ipv4/dns_cache.c index 7d50c88..b2c5545 100644 --- a/net/ipv4/dns_cache.c +++ b/net/ipv4/dns_cache.c @@ -1,5 +1,11 @@ -// DNS Resolver Cache with TTL Management and Metrics for SUB-OS +// DNS Resolver Cache with TTL Management and Metrics for SUB-OS. +// +// IPs are stored in the same convention as the rest of the network stack: +// network byte order in memory (the first octet in the lowest byte), exactly as +// produced by ip_parse() and consumed by ip_to_str(). Storing them any other +// way would hand dns_resolve() callers a byte-swapped address. #include +#include #include #include #include @@ -10,12 +16,12 @@ static uint64_t total_hits = 0; void dns_cache_init(void) { memset(dns_table, 0, sizeof(dns_table)); - // Seed common local and well-known hosts - dns_cache_insert("localhost", 0x7F000001, 86400); // 127.0.0.1 - dns_cache_insert("gateway.local", 0x0A000202, 3600); // 10.0.2.2 - dns_cache_insert("dns.google", 0x08080808, 7200); // 8.8.8.8 - dns_cache_insert("one.one.one.one", 0x01010101, 7200); // 1.1.1.1 - dns_cache_insert("github.com", 0x8C527964, 300); // 140.82.121.4 + // Seed common local and well-known hosts (network byte order via ip_parse). + dns_cache_insert("localhost", ip_parse("127.0.0.1"), 86400); + dns_cache_insert("gateway.local", ip_parse("10.0.2.2"), 3600); + dns_cache_insert("dns.google", ip_parse("8.8.8.8"), 7200); + dns_cache_insert("one.one.one.one", ip_parse("1.1.1.1"), 7200); + dns_cache_insert("github.com", ip_parse("140.82.121.4"), 300); printk(KERN_INFO "DNS: High-performance DNS Cache table initialized (Capacity: %d)\n", DNS_CACHE_MAX_ENTRIES); } @@ -63,7 +69,7 @@ void dns_cache_insert(const char* hostname, uint32_t ip, uint32_t ttl) { void dns_cache_flush(void) { memset(dns_table, 0, sizeof(dns_table)); - dns_cache_insert("localhost", 0x7F000001, 86400); + dns_cache_insert("localhost", ip_parse("127.0.0.1"), 86400); printk(ANSI_YELLOW "DNS: Resolver cache flushed.\n" ANSI_RESET); } @@ -78,11 +84,8 @@ void dns_cache_dump(void) { for (int i = 0; i < DNS_CACHE_MAX_ENTRIES; i++) { if (dns_table[i].active) { - uint32_t ip = dns_table[i].ip; char ip_str[20]; - snprintf(ip_str, sizeof(ip_str), "%u.%u.%u.%u", - (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, - (ip >> 8) & 0xFF, ip & 0xFF); + ip_to_str(dns_table[i].ip, ip_str); printk("%-28s " ANSI_BRIGHT_YELLOW "%-16s" ANSI_RESET " %8u %8llu\n", dns_table[i].hostname, ip_str, diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index 8b9becc..13ea069 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -1,26 +1,226 @@ +// UDP (User Datagram Protocol) transport for SUB-OS. +// +// Provides connectionless datagram delivery on top of the IPv4 layer. Inbound +// datagrams are demultiplexed by destination port to a small table of bound +// handlers (used by the socket layer, the DNS resolver and the DHCP client); +// unclaimed ports are counted as drops. Outbound datagrams are checksummed with +// the standard IPv4 pseudo-header. #include +#include #include +#include #include +#define UDP_MAX_BINDINGS 32 +#define UDP_EPHEMERAL_LO 49152u +#define UDP_EPHEMERAL_HI 65535u + +typedef struct { + uint16_t port; + udp_handler_t handler; + void* ctx; + bool active; +} udp_binding_t; + +// Pseudo-header used only for checksum computation (never transmitted). +typedef struct { + uint32_t src_ip; + uint32_t dst_ip; + uint8_t zero; + uint8_t protocol; + uint16_t udp_len; +} __attribute__((packed)) udp_pseudo_header_t; + +static udp_binding_t bindings[UDP_MAX_BINDINGS]; +static uint16_t next_ephemeral = UDP_EPHEMERAL_LO; + +static volatile uint64_t stat_rx = 0; +static volatile uint64_t stat_tx = 0; +static volatile uint64_t stat_drop = 0; + +static uint16_t udp_checksum(uint32_t src_ip, uint32_t dst_ip, + const udp_header_t* udp, const void* payload, + uint16_t payload_len) { + udp_pseudo_header_t ph; + ph.src_ip = src_ip; + ph.dst_ip = dst_ip; + ph.zero = 0; + ph.protocol = IP_PROTO_UDP; + ph.udp_len = htons((uint16_t)(sizeof(udp_header_t) + payload_len)); + + uint32_t sum = 0; + const uint16_t* p = (const uint16_t*)&ph; + for (size_t i = 0; i < sizeof(ph) / 2; i++) sum += *p++; + + p = (const uint16_t*)udp; + for (size_t i = 0; i < sizeof(udp_header_t) / 2; i++) sum += *p++; + + if (payload && payload_len > 0) { + p = (const uint16_t*)payload; + size_t len = payload_len; + while (len > 1) { sum += *p++; len -= 2; } + if (len > 0) sum += *(const uint8_t*)p; + } + + while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16); + uint16_t cksum = (uint16_t)(~sum); + // A zero checksum means "not computed" on the wire; send 0xFFFF instead. + return cksum ? cksum : 0xFFFF; +} + void udp_init(void) { + memset(bindings, 0, sizeof(bindings)); + next_ephemeral = UDP_EPHEMERAL_LO; + stat_rx = stat_tx = stat_drop = 0; printk(KERN_INFO "NET: UDP (User Datagram Protocol) transport layer online\n"); } -void udp_receive(const uint8_t* packet, uint16_t length, uint32_t src_ip) { - (void)packet; (void)length; (void)src_ip; +int udp_bind(uint16_t port, udp_handler_t handler, void* ctx) { + if (!handler || port == 0) return -1; + for (int i = 0; i < UDP_MAX_BINDINGS; i++) { + if (bindings[i].active && bindings[i].port == port) return -1; + } + for (int i = 0; i < UDP_MAX_BINDINGS; i++) { + if (!bindings[i].active) { + bindings[i].port = port; + bindings[i].handler = handler; + bindings[i].ctx = ctx; + bindings[i].active = true; + return 0; + } + } + return -1; } -void udp_send(uint32_t dst_ip, uint16_t src_port, uint16_t dst_port, const void* data, uint16_t len) { - uint8_t buffer[1500]; - udp_header_t* udp = (udp_header_t*)buffer; +void udp_unbind(uint16_t port) { + for (int i = 0; i < UDP_MAX_BINDINGS; i++) { + if (bindings[i].active && bindings[i].port == port) { + bindings[i].active = false; + bindings[i].handler = NULL; + bindings[i].ctx = NULL; + return; + } + } +} + +uint16_t udp_alloc_ephemeral_port(void) { + for (uint32_t tries = 0; tries <= (UDP_EPHEMERAL_HI - UDP_EPHEMERAL_LO); tries++) { + uint16_t candidate = next_ephemeral; + if (next_ephemeral >= UDP_EPHEMERAL_HI) next_ephemeral = UDP_EPHEMERAL_LO; + else next_ephemeral++; + + bool taken = false; + for (int i = 0; i < UDP_MAX_BINDINGS; i++) { + if (bindings[i].active && bindings[i].port == candidate) { taken = true; break; } + } + if (!taken) return candidate; + } + return 0; +} + +void udp_receive(const uint8_t* packet, uint16_t length, uint32_t src_ip) { + if (!packet || length < sizeof(udp_header_t)) { stat_drop++; return; } + + const udp_header_t* udp = (const udp_header_t*)packet; + uint16_t dst_port = ntohs(udp->dst_port); + uint16_t src_port = ntohs(udp->src_port); + uint16_t udp_len = ntohs(udp->length); + + if (udp_len < sizeof(udp_header_t) || udp_len > length) { stat_drop++; return; } + + const uint8_t* data = packet + sizeof(udp_header_t); + uint16_t data_len = udp_len - sizeof(udp_header_t); + + stat_rx++; + + for (int i = 0; i < UDP_MAX_BINDINGS; i++) { + if (bindings[i].active && bindings[i].port == dst_port && bindings[i].handler) { + bindings[i].handler(src_ip, src_port, dst_port, data, data_len, bindings[i].ctx); + return; + } + } + // No listener on this port. + stat_drop++; +} + +void udp_send(uint32_t dst_ip, uint16_t src_port, uint16_t dst_port, + const void* data, uint16_t len) { + uint16_t total = (uint16_t)(sizeof(udp_header_t) + len); + uint8_t* buf = (uint8_t*)kzalloc(total); + if (!buf) return; + + udp_header_t* udp = (udp_header_t*)buf; udp->src_port = htons(src_port); udp->dst_port = htons(dst_port); - udp->length = htons((uint16_t)(sizeof(udp_header_t) + len)); + udp->length = htons(total); udp->checksum = 0; - if (data && len > 0) { - memcpy(buffer + sizeof(udp_header_t), data, len); + if (data && len > 0) memcpy(buf + sizeof(udp_header_t), data, len); + + net_if_t* nif = net_get_primary_if(); + uint32_t src_ip = nif ? nif->ip : 0; + udp->checksum = udp_checksum(src_ip, dst_ip, udp, data, len); + + net_send_ip(dst_ip, IP_PROTO_UDP, buf, total); + stat_tx++; + kfree(buf); +} + +void udp_send_broadcast(uint16_t src_port, uint16_t dst_port, + const void* data, uint16_t len) { + // Build IPv4 + UDP by hand: src 0.0.0.0, dst 255.255.255.255, sent to the + // Ethernet broadcast address without ARP (the interface has no IP yet). + static const uint8_t bcast_mac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + uint16_t udp_total = (uint16_t)(sizeof(udp_header_t) + len); + uint16_t ip_total = (uint16_t)(sizeof(ip_header_t) + udp_total); + + uint8_t* buf = (uint8_t*)kzalloc(ip_total); + if (!buf) return; + + ip_header_t* ip = (ip_header_t*)buf; + ip->ihl_version = 0x45; + ip->tos = 0; + ip->total_length = htons(ip_total); + ip->id = htons(0x5342); // 'SB' + ip->flags_fragment = 0; + ip->ttl = 64; + ip->protocol = IP_PROTO_UDP; + ip->checksum = 0; + ip->src_ip = 0x00000000; + ip->dst_ip = 0xFFFFFFFF; + + // IPv4 header checksum. + { + uint32_t sum = 0; + const uint16_t* p = (const uint16_t*)ip; + for (size_t i = 0; i < sizeof(ip_header_t) / 2; i++) sum += *p++; + while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16); + ip->checksum = (uint16_t)(~sum); } - net_send_ip(dst_ip, IP_PROTO_UDP, buffer, (uint16_t)(sizeof(udp_header_t) + len)); + udp_header_t* udp = (udp_header_t*)(buf + sizeof(ip_header_t)); + udp->src_port = htons(src_port); + udp->dst_port = htons(dst_port); + udp->length = htons(udp_total); + udp->checksum = 0; + if (data && len > 0) memcpy((uint8_t*)udp + sizeof(udp_header_t), data, len); + udp->checksum = udp_checksum(0x00000000, 0xFFFFFFFF, udp, data, len); + + net_send_eth(bcast_mac, ETHERTYPE_IPV4, buf, ip_total); + stat_tx++; + kfree(buf); +} + +void udp_get_stats(uint64_t* rx, uint64_t* tx, uint64_t* drop) { + if (rx) *rx = stat_rx; + if (tx) *tx = stat_tx; + if (drop) *drop = stat_drop; +} + +int udp_get_bindings(uint16_t* ports_out, int max) { + int n = 0; + for (int i = 0; i < UDP_MAX_BINDINGS && n < max; i++) { + if (bindings[i].active) ports_out[n++] = bindings[i].port; + } + return n; } diff --git a/userland/lazybox/lazybox.c b/userland/lazybox/lazybox.c index f2f95f6..5733dac 100644 --- a/userland/lazybox/lazybox.c +++ b/userland/lazybox/lazybox.c @@ -5,6 +5,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -882,16 +885,35 @@ static int applet_rmmod(int argc, char** argv) { static int applet_ifconfig(int argc, char** argv) { (void)argc; (void)argv; + net_if_t* nif = net_get_primary_if(); uint8_t mac[6]; e1000_get_mac(mac); - printk("eth0: flags=4163 mtu 1500\n"); - printk(" inet 10.0.2.15 netmask 255.255.255.0 broadcast 10.0.2.2\n"); + char ip[16], mask[16], gw[16], dns[16]; + if (nif) { + ip_to_str(nif->ip, ip); + ip_to_str(nif->subnet, mask); + ip_to_str(nif->gateway, gw); + ip_to_str(nif->dns, dns); + } else { + strcpy(ip, "0.0.0.0"); strcpy(mask, "0.0.0.0"); + strcpy(gw, "0.0.0.0"); strcpy(dns, "0.0.0.0"); + } + + printk("%s: flags=4163 mtu 1500\n", + nif ? nif->name : "eth0"); + printk(" inet %s netmask %s gateway %s\n", ip, mask, gw); + printk(" nameserver %s\n", dns); printk(" ether %02x:%02x:%02x:%02x:%02x:%02x txqueuelen 1000 (Ethernet)\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - printk(" RX packets 0 bytes 0\n"); - printk(" TX packets 0 bytes 0\n"); + printk(" RX packets %llu bytes %llu\n", + e1000_get_rx_packets(), e1000_get_rx_bytes()); + printk(" TX packets %llu bytes %llu\n", + e1000_get_tx_packets(), e1000_get_tx_bytes()); printk(" Link Status: %s\n", e1000_is_link_up() ? "UP" : "DOWN"); + + printk("\nlo: flags=73 mtu 65536\n"); + printk(" inet 127.0.0.1 netmask 255.0.0.0\n"); return 0; } @@ -924,11 +946,20 @@ static int applet_dhclient(int argc, char** argv) { static int applet_nslookup(int argc, char** argv) { const char* host = (argc >= 2) ? argv[1] : "google.com"; + net_if_t* nif = net_get_primary_if(); + char srv[16]; + ip_to_str(nif ? nif->dns : ip_parse("10.0.2.3"), srv); + uint32_t ip = dns_resolve(host); + if (ip == 0) { + printk("Server:\t\t%s\nAddress:\t%s#53\n\n** server can't find %s: NXDOMAIN\n", + srv, srv, host); + return 1; + } char ip_buf[16]; ip_to_str(ip, ip_buf); - printk("Server: 10.0.2.3\nAddress: 10.0.2.3#53\n\nNon-authoritative answer:\nName: %s\nAddress: %s\n", - host, ip_buf); + printk("Server:\t\t%s\nAddress:\t%s#53\n\nNon-authoritative answer:\nName:\t%s\nAddress: %s\n", + srv, srv, host, ip_buf); return 0; } @@ -2165,9 +2196,6 @@ static int applet_netstat(int argc, char** argv) { if (httpd_is_running()) { printk("tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN (httpd)\n"); } - printk("udp 0 0 0.0.0.0:53 0.0.0.0:* LISTEN (dns)\n"); - printk("udp 0 0 0.0.0.0:68 0.0.0.0:* ESTABLISHED (dhcp)\n"); - for (size_t i = 0; i < 8; i++) { const ssh_session_t* s = sshd_get_session(i); if (s && s->in_use) { @@ -2177,6 +2205,55 @@ static int applet_netstat(int argc, char** argv) { client_ip_str, s->client_port, s->username); } } + + // Live UDP port bindings (DNS resolver, DHCP client, datagram sockets). + uint16_t ports[32]; + int np = udp_get_bindings(ports, 32); + for (int i = 0; i < np; i++) { + const char* svc = ""; + if (ports[i] == 53) svc = " (dns)"; + else if (ports[i] == 68) svc = " (dhcp)"; + else if (ports[i] >= 49152) svc = " (ephemeral)"; + printk("udp 0 0 0.0.0.0:%-5u 0.0.0.0:*%s\n", ports[i], svc); + } + + uint64_t urx, utx, udrop; + udp_get_stats(&urx, &utx, &udrop); + printk(ANSI_YELLOW "\nTransport statistics:\n" ANSI_RESET); + printk(" udp: %llu datagrams received, %llu sent, %llu dropped\n", urx, utx, udrop); + printk(" tcp: %u active connection(s)\n", (unsigned)tcp_get_connections_count()); + return 0; +} + +// Datagram socket table + UDP transport statistics. +static int applet_sockstat(int argc, char** argv) { + (void)argc; (void)argv; + printk(ANSI_BRIGHT_CYAN "=== SUB-OS Socket Table ===\n" ANSI_RESET); + printk(ANSI_BOLD "%-4s %-8s %-24s %-24s %s\n" ANSI_RESET, + "FD", "TYPE", "LOCAL", "REMOTE", "STATE"); + + int shown = 0; + for (int i = 0; i < 32; i++) { + const socket_t* s = socket_get(i); + if (!s) continue; + char la[16], ra[16]; + ip_to_str(s->local_ip, la); + ip_to_str(s->remote_ip, ra); + char lbuf[24], rbuf[24]; + snprintf(lbuf, sizeof(lbuf), "%s:%u", la, s->local_port); + snprintf(rbuf, sizeof(rbuf), "%s:%u", ra, s->remote_port); + const char* ty = (s->type == SOCK_DGRAM) ? "dgram" : + (s->type == SOCK_STREAM) ? "stream" : "raw"; + printk("%-4d %-8s %-24s %-24s %s\n", i, ty, lbuf, rbuf, + s->state == 1 ? "CONNECTED" : "OPEN"); + shown++; + } + if (!shown) printk(" (no open sockets)\n"); + + uint64_t tx, rx, drops; + socket_get_stats(&tx, &rx, &drops); + printk("\nSocket I/O: %llu datagrams sent, %llu received, %llu dropped\n", + tx, rx, drops); return 0; } @@ -2495,6 +2572,7 @@ static const lazybox_applet_t applets[] = { {"nslookup", applet_nslookup, "nslookup ", "Query DNS name servers", "Network"}, {"iptables", applet_iptables, "iptables", "Firewall packet filter rules","Network"}, {"netstat", applet_netstat, "netstat", "Network port connections", "Network"}, + {"sockstat", applet_sockstat, "sockstat", "Socket table & UDP stats", "Network"}, // Storage & Devices {"lsblk", applet_lsblk, "lsblk", "List block storage devices", "Storage"},