diff --git a/README.md b/README.md index 81b2c08..0cc4171 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ Once a task is completed, it should be checked off. - [x] Create a network driver for the NE2000 NIC. - [x] Create a network driver for the 3C509B NIC. It should only support RJ45 and 10base-T. - [x] Create an ethernet subsytsem. Each ethernet device should be shown as a character device with the name `ethN`. -- [ ] Create a IPv4 stack. Create the `ip` app that shows curernt IPv4 configuration. It should read this information from `/sys` +- [x] Create a IPv4 stack. Create the `ip` app that shows curernt IPv4 configuration. It should read this information from `/sys` - [ ] Create a ARP subsystem. Create the `arp` command that shows current ARP tables. Again, this info should be found in `/sys` - [ ] Create a DHCP subsystem. Create the `dhcp` command to show current DHCP status information. - [ ] Create a UDP and TCP stack. diff --git a/apps/ip/ip.c b/apps/ip/ip.c new file mode 100644 index 0000000..6bccd70 --- /dev/null +++ b/apps/ip/ip.c @@ -0,0 +1,202 @@ +/** + * @file ip.c + * @brief Display and configure IPv4 network configuration. + * + * Reads network interface information from /sys/net and displays + * the current IPv4 configuration for all interfaces. + * + * Usage: + * ip - Show all interfaces + * ip set - Configure an interface + * + * Examples: + * ip + * ip set eth1 192.168.1.100 255.255.255.0 192.168.1.1 + */ + +#include "syscalls.h" + +/** + * Read the contents of a sysfs file into buf. + * Returns number of bytes read, or -1 on failure. + */ +static int32_t read_sysfs(const char *path, char *buf, uint32_t size) { + int32_t fd = open(path, 0); + if (fd < 0) return -1; + + int32_t n = read(fd, buf, size - 1); + close(fd); + + if (n > 0) { + buf[n] = '\0'; + /* Strip trailing newline */ + for (int32_t i = n - 1; i >= 0; i--) { + if (buf[i] == '\n' || buf[i] == '\r') buf[i] = '\0'; + else break; + } + } else { + buf[0] = '\0'; + } + return n; +} + +/** + * Build a sysfs path: /sys/net// + */ +static void build_path(char *out, uint32_t out_size, + const char *iface, const char *file) { + /* Manual string concatenation */ + uint32_t pos = 0; + const char *prefix = "/sys/net/"; + while (*prefix && pos < out_size - 1) out[pos++] = *prefix++; + while (*iface && pos < out_size - 1) out[pos++] = *iface++; + if (pos < out_size - 1) out[pos++] = '/'; + while (*file && pos < out_size - 1) out[pos++] = *file++; + out[pos] = '\0'; +} + +/** + * Show info for one interface. + */ +static void show_iface(const char *name) { + char path[128]; + char val[64]; + + puts(name); + puts(":\n"); + + /* MAC address */ + build_path(path, sizeof(path), name, "mac"); + if (read_sysfs(path, val, sizeof(val)) > 0) { + puts(" MAC: "); + puts(val); + puts("\n"); + } + + /* Link status */ + build_path(path, sizeof(path), name, "link"); + if (read_sysfs(path, val, sizeof(val)) > 0) { + puts(" Link: "); + puts(val); + puts("\n"); + } + + /* IP address */ + build_path(path, sizeof(path), name, "ip"); + if (read_sysfs(path, val, sizeof(val)) > 0) { + puts(" IP: "); + puts(val); + puts("\n"); + } + + /* Netmask */ + build_path(path, sizeof(path), name, "netmask"); + if (read_sysfs(path, val, sizeof(val)) > 0) { + puts(" Netmask: "); + puts(val); + puts("\n"); + } + + /* Gateway */ + build_path(path, sizeof(path), name, "gateway"); + if (read_sysfs(path, val, sizeof(val)) > 0) { + puts(" Gateway: "); + puts(val); + puts("\n"); + } +} + +/** + * Write a value to a sysfs file. + */ +static int32_t write_sysfs(const char *path, const char *value) { + int32_t fd = open(path, 0); + if (fd < 0) return -1; + + int32_t n = write(fd, value, strlen(value)); + close(fd); + return n; +} + +int main(void) { + char arg1[64]; + + /* Check if we have a subcommand */ + if (getenv("ARG1", arg1, sizeof(arg1)) < 0 || arg1[0] == '\0') { + /* No arguments — show all interfaces */ + char name[128]; + uint32_t idx = 0; + int found = 0; + + while (readdir("/sys/net", idx, name) >= 0) { + show_iface(name); + found = 1; + idx++; + } + + if (!found) { + puts("No network interfaces found.\n"); + } + return 0; + } + + /* Check for "set" subcommand */ + if (strcmp(arg1, "set") == 0) { + char iface_name[32], ip[32], netmask[32], gateway[32]; + char path[128]; + + if (getenv("ARG2", iface_name, sizeof(iface_name)) < 0) { + puts("Usage: ip set \n"); + return 1; + } + if (getenv("ARG3", ip, sizeof(ip)) < 0) { + puts("Usage: ip set \n"); + return 1; + } + if (getenv("ARG4", netmask, sizeof(netmask)) < 0) { + puts("Usage: ip set \n"); + return 1; + } + if (getenv("ARG5", gateway, sizeof(gateway)) < 0) { + puts("Usage: ip set \n"); + return 1; + } + + /* Write IP */ + build_path(path, sizeof(path), iface_name, "ip"); + if (write_sysfs(path, ip) < 0) { + puts("Failed to set IP address\n"); + return 1; + } + + /* Write netmask */ + build_path(path, sizeof(path), iface_name, "netmask"); + if (write_sysfs(path, netmask) < 0) { + puts("Failed to set netmask\n"); + return 1; + } + + /* Write gateway */ + build_path(path, sizeof(path), iface_name, "gateway"); + if (write_sysfs(path, gateway) < 0) { + puts("Failed to set gateway\n"); + return 1; + } + + puts("Configured "); + puts(iface_name); + puts(": "); + puts(ip); + puts(" / "); + puts(netmask); + puts(" gw "); + puts(gateway); + puts("\n"); + return 0; + } + + puts("Unknown command: "); + puts(arg1); + puts("\nUsage: ip [set ]\n"); + return 1; +} diff --git a/build.log b/build.log index 5373539..6c5bbbe 100644 --- a/build.log +++ b/build.log @@ -1,3 +1,6 @@ +-- Configuring done (0.1s) +-- Generating done (0.2s) +-- Build files have been written to: /workspaces/claude-os/build [ 2%] Building user-mode applications Building app: cat Built: /workspaces/claude-os/build/apps_bin/cat (310 bytes) @@ -11,6 +14,8 @@ Building app: fork-test Built: /workspaces/claude-os/build/apps_bin/fork-test (132 bytes) Building app: hello-world Built: /workspaces/claude-os/build/apps_bin/hello-world (49 bytes) +Building app: ip + Built: /workspaces/claude-os/build/apps_bin/ip (3695 bytes) Building app: ls Built: /workspaces/claude-os/build/apps_bin/ls (250 bytes) Building app: mkfs.fat32 @@ -29,7 +34,12 @@ Building app: sh 1 warning generated. Built: /workspaces/claude-os/build/apps_bin/sh (3428 bytes) [ 2%] Built target apps +[ 5%] Generating CPIO initial ramdisk +Generated initrd: 24100 bytes [ 5%] Built target initrd +[ 8%] Building C object src/CMakeFiles/kernel.dir/kernel.c.o +[ 11%] Building C object src/CMakeFiles/kernel.dir/ipv4.c.o +[ 13%] Linking C executable ../bin/kernel [ 97%] Built target kernel [100%] Generating bootable ISO image xorriso 1.5.6 : RockRidge filesystem manipulator, libburnia project. @@ -38,14 +48,14 @@ Drive current: -outdev 'stdio:/workspaces/claude-os/release/claude-os.iso' Media current: stdio file, overwriteable Media status : is blank Media summary: 0 sessions, 0 data blocks, 0 data, 126g free -Added to ISO image: directory '/'='/tmp/grub.KhfgHk' +Added to ISO image: directory '/'='/tmp/grub.JjepBC' xorriso : UPDATE : 581 files added in 1 seconds Added to ISO image: directory '/'='/workspaces/claude-os/build/isodir' xorriso : UPDATE : 586 files added in 1 seconds xorriso : NOTE : Copying to System Area: 512 bytes from file '/usr/lib/grub/i386-pc/boot_hybrid.img' -xorriso : UPDATE : Thank you for being patient. Working since 0 seconds. -ISO image produced: 5967 sectors -Written to medium : 5967 sectors at LBA 0 +xorriso : UPDATE : 0.27% done +ISO image produced: 5974 sectors +Written to medium : 5974 sectors at LBA 0 Writing to 'stdio:/workspaces/claude-os/release/claude-os.iso' completed successfully. [100%] Built target iso diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 48f3dd0..ef22ea1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -28,6 +28,7 @@ add_executable(kernel ne2000.c e3c509.c ethernet.c + ipv4.c env.c keyboard.c interrupts.S diff --git a/src/ipv4.c b/src/ipv4.c new file mode 100644 index 0000000..c4661f4 --- /dev/null +++ b/src/ipv4.c @@ -0,0 +1,279 @@ +/** + * @file ipv4.c + * @brief IPv4 network layer implementation. + * + * Handles IPv4 packet construction, parsing, checksum calculation, + * and routing to the appropriate Ethernet interface. + * + * Incoming Ethernet frames with EtherType 0x0800 are passed to + * ipv4_receive(), which validates the header and dispatches to + * registered protocol handlers (ICMP, UDP, TCP, etc.). + */ + +#include "ipv4.h" +#include "ethernet.h" +#include + +/* Debug print helpers */ +extern void offset_print(const char *str); +extern void print_hex(uint32_t val); + +/* ================================================================ + * Global state + * ================================================================ */ + +/** Maximum number of registered protocol handlers. */ +#define MAX_PROTO_HANDLERS 16 + +/** Protocol handler entry. */ +typedef struct { + uint8_t protocol; + ipv4_proto_handler_t handler; +} proto_handler_t; + +/** Registered protocol handlers. */ +static proto_handler_t proto_handlers[MAX_PROTO_HANDLERS]; +static uint32_t proto_handler_count = 0; + +/** Global IP identification counter. */ +static uint16_t ip_id_counter = 1; + +/* ================================================================ + * Checksum + * ================================================================ */ + +uint16_t ipv4_checksum(const void *data, uint32_t len) { + const uint16_t *words = (const uint16_t *)data; + uint32_t sum = 0; + + while (len > 1) { + sum += *words++; + len -= 2; + } + + /* Add left-over byte, if any */ + if (len == 1) { + sum += *(const uint8_t *)words; + } + + /* Fold 32-bit sum into 16 bits */ + while (sum >> 16) { + sum = (sum & 0xFFFF) + (sum >> 16); + } + + return (uint16_t)(~sum); +} + +/* ================================================================ + * Address conversion helpers + * ================================================================ */ + +uint32_t ipv4_aton(const char *str) { + uint32_t octets[4] = {0}; + int idx = 0; + + for (const char *p = str; *p && idx < 4; p++) { + if (*p == '.') { + idx++; + } else if (*p >= '0' && *p <= '9') { + octets[idx] = octets[idx] * 10 + (uint32_t)(*p - '0'); + } else { + break; + } + } + + return (octets[0] << 24) | (octets[1] << 16) | + (octets[2] << 8) | octets[3]; +} + +char *ipv4_ntoa(uint32_t ip, char *buf, uint32_t size) { + if (size < 16) { buf[0] = '\0'; return buf; } + + int pos = 0; + for (int i = 0; i < 4; i++) { + if (i > 0) buf[pos++] = '.'; + uint8_t octet = (uint8_t)((ip >> (24 - i * 8)) & 0xFF); + if (octet >= 100) { + buf[pos++] = (char)('0' + octet / 100); + buf[pos++] = (char)('0' + (octet % 100) / 10); + buf[pos++] = (char)('0' + octet % 10); + } else if (octet >= 10) { + buf[pos++] = (char)('0' + octet / 10); + buf[pos++] = (char)('0' + octet % 10); + } else { + buf[pos++] = (char)('0' + octet); + } + } + buf[pos] = '\0'; + return buf; +} + +/* ================================================================ + * Routing + * ================================================================ */ + +/** + * Find the Ethernet interface for a given destination IP. + * + * Simple routing: check if dst is on a directly-connected subnet. + * If not, use the first interface with a configured gateway. + * Fallback: first interface. + * + * @param dst_ip Destination IP (host byte order). + * @return Interface index, or -1 if no interfaces. + */ +static int ipv4_route(uint32_t dst_ip) { + uint32_t count = ethernet_get_iface_count(); + if (count == 0) return -1; + + /* Check for directly-connected subnet */ + for (uint32_t i = 0; i < count; i++) { + eth_iface_t *iface = ethernet_get_iface(i); + if (!iface || !iface->active) continue; + if (iface->ip_addr == 0 || iface->netmask == 0) continue; + + if ((dst_ip & iface->netmask) == (iface->ip_addr & iface->netmask)) { + return (int)i; + } + } + + /* Use first interface with a gateway */ + for (uint32_t i = 0; i < count; i++) { + eth_iface_t *iface = ethernet_get_iface(i); + if (!iface || !iface->active) continue; + if (iface->gateway != 0) return (int)i; + } + + /* Fallback: first active interface */ + for (uint32_t i = 0; i < count; i++) { + eth_iface_t *iface = ethernet_get_iface(i); + if (iface && iface->active) return (int)i; + } + + return -1; +} + +/* ================================================================ + * Send + * ================================================================ */ + +int ipv4_send_iface(uint32_t iface_idx, uint32_t dst_ip, uint8_t protocol, + const void *payload, uint32_t len) { + if (len > IPV4_MTU) return -1; + + eth_iface_t *iface = ethernet_get_iface(iface_idx); + if (!iface || !iface->active) return -1; + + /* Build IPv4 packet on stack */ + uint8_t packet[ETH_MTU]; + ipv4_header_t *hdr = (ipv4_header_t *)packet; + + hdr->ihl_version = 0x45; /* IPv4, IHL=5 (20 bytes) */ + hdr->tos = 0; + hdr->total_length = htons((uint16_t)(IPV4_HLEN + len)); + hdr->identification = htons(ip_id_counter++); + hdr->flags_fragoff = htons(IPV4_FLAG_DF); /* Don't fragment */ + hdr->ttl = 64; + hdr->protocol = protocol; + hdr->checksum = 0; + hdr->src_ip = htonl(iface->ip_addr); + hdr->dst_ip = htonl(dst_ip); + + /* Compute header checksum */ + hdr->checksum = ipv4_checksum(hdr, IPV4_HLEN); + + /* Copy payload */ + memcpy(packet + IPV4_HLEN, payload, len); + + /* Determine destination MAC. + * For now, use broadcast (FF:FF:FF:FF:FF:FF) — the ARP subsystem + * will override this once implemented. */ + uint8_t dst_mac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + + /* TODO: ARP lookup for dst_ip (or gateway if not on-link) */ + + return ethernet_send(iface, dst_mac, ETHERTYPE_IPV4, packet, + IPV4_HLEN + len); +} + +int ipv4_send(uint32_t dst_ip, uint8_t protocol, + const void *payload, uint32_t len) { + int iface_idx = ipv4_route(dst_ip); + if (iface_idx < 0) return -1; + return ipv4_send_iface((uint32_t)iface_idx, dst_ip, protocol, payload, len); +} + +/* ================================================================ + * Receive + * ================================================================ */ + +void ipv4_receive(const void *data, uint32_t len, uint32_t iface_idx) { + if (len < IPV4_HLEN) return; + + const ipv4_header_t *hdr = (const ipv4_header_t *)data; + + /* Verify version */ + if ((hdr->ihl_version >> 4) != 4) return; + + /* Verify header length */ + uint32_t ihl = (uint32_t)(hdr->ihl_version & 0x0F) * 4; + if (ihl < IPV4_HLEN || ihl > len) return; + + /* Verify checksum */ + if (ipv4_checksum(data, ihl) != 0) return; + + uint16_t total_len = ntohs(hdr->total_length); + if (total_len > len) return; + + uint32_t src_ip = ntohl(hdr->src_ip); + uint32_t dst_ip = ntohl(hdr->dst_ip); + const uint8_t *payload = (const uint8_t *)data + ihl; + uint32_t payload_len = total_len - ihl; + + /* Check if this packet is for us */ + eth_iface_t *iface = ethernet_get_iface(iface_idx); + if (iface && iface->ip_addr != 0) { + if (dst_ip != iface->ip_addr && + dst_ip != 0xFFFFFFFF && /* broadcast */ + (dst_ip & ~iface->netmask) != ~iface->netmask) { + return; /* Not for us */ + } + } + + /* Dispatch to registered protocol handler */ + for (uint32_t i = 0; i < proto_handler_count; i++) { + if (proto_handlers[i].protocol == hdr->protocol) { + proto_handlers[i].handler(src_ip, dst_ip, payload, + payload_len, iface_idx); + return; + } + } + + /* No handler registered for this protocol */ +} + +/* ================================================================ + * Protocol registration + * ================================================================ */ + +int ipv4_register_proto(uint8_t protocol, ipv4_proto_handler_t handler) { + if (proto_handler_count >= MAX_PROTO_HANDLERS) return -1; + + proto_handlers[proto_handler_count].protocol = protocol; + proto_handlers[proto_handler_count].handler = handler; + proto_handler_count++; + + return 0; +} + +/* ================================================================ + * Initialization + * ================================================================ */ + +void ipv4_init(void) { + memset(proto_handlers, 0, sizeof(proto_handlers)); + proto_handler_count = 0; + ip_id_counter = 1; + + offset_print(" IPv4: initialized\n"); +} diff --git a/src/ipv4.h b/src/ipv4.h new file mode 100644 index 0000000..89a4e3e --- /dev/null +++ b/src/ipv4.h @@ -0,0 +1,158 @@ +/** + * @file ipv4.h + * @brief IPv4 network layer. + * + * Provides IPv4 packet construction, parsing, and routing. + * Sits on top of the Ethernet subsystem and provides the foundation + * for higher-level protocols (ICMP, UDP, TCP). + * + * The IPv4 stack maintains a simple routing table: each Ethernet + * interface has an IP address, netmask, and default gateway. + * Outbound packets are routed to the appropriate interface. + * + * Inbound packets are dispatched to registered protocol handlers + * based on the IP protocol field. + */ + +#ifndef IPV4_H +#define IPV4_H + +#include + +/* ================================================================ + * IPv4 header (20 bytes minimum, no options) + * ================================================================ */ + +/** IPv4 header structure (network byte order in memory). */ +typedef struct __attribute__((packed)) ipv4_header { + uint8_t ihl_version; /**< Version (4) and IHL (5 for no options). */ + uint8_t tos; /**< Type of Service. */ + uint16_t total_length; /**< Total datagram length (header + data). */ + uint16_t identification; /**< Fragment identification. */ + uint16_t flags_fragoff; /**< Flags (3 bits) + Fragment Offset (13 bits). */ + uint8_t ttl; /**< Time to Live. */ + uint8_t protocol; /**< Upper-layer protocol number. */ + uint16_t checksum; /**< Header checksum. */ + uint32_t src_ip; /**< Source IP address (network byte order). */ + uint32_t dst_ip; /**< Destination IP address (network byte order). */ +} ipv4_header_t; + +/** IPv4 header size (no options). */ +#define IPV4_HLEN 20 + +/** Maximum IPv4 payload over Ethernet (1500 - 20). */ +#define IPV4_MTU 1480 + +/* ================================================================ + * Protocol numbers + * ================================================================ */ +#define IP_PROTO_ICMP 1 +#define IP_PROTO_TCP 6 +#define IP_PROTO_UDP 17 + +/* ================================================================ + * IPv4 flags + * ================================================================ */ +#define IPV4_FLAG_DF 0x4000 /**< Don't Fragment. */ +#define IPV4_FLAG_MF 0x2000 /**< More Fragments. */ + +/* ================================================================ + * Protocol handler callback + * ================================================================ */ + +/** + * Callback for handling incoming IPv4 packets of a specific protocol. + * + * @param src_ip Source IP address (host byte order). + * @param dst_ip Destination IP address (host byte order). + * @param payload Protocol payload (past IPv4 header). + * @param len Payload length. + * @param iface_idx Ethernet interface index the packet arrived on. + */ +typedef void (*ipv4_proto_handler_t)(uint32_t src_ip, uint32_t dst_ip, + const void *payload, uint32_t len, + uint32_t iface_idx); + +/* ================================================================ + * Public API + * ================================================================ */ + +/** + * Initialize the IPv4 subsystem. + */ +void ipv4_init(void); + +/** + * Send an IPv4 packet. + * + * Automatically routes to the correct Ethernet interface based on + * the destination IP and configured routes. + * + * @param dst_ip Destination IP (host byte order). + * @param protocol IP protocol number (e.g., IP_PROTO_UDP). + * @param payload Packet payload. + * @param len Payload length (max IPV4_MTU). + * @return 0 on success, -1 on failure. + */ +int ipv4_send(uint32_t dst_ip, uint8_t protocol, + const void *payload, uint32_t len); + +/** + * Send an IPv4 packet through a specific interface. + * + * @param iface_idx Ethernet interface index. + * @param dst_ip Destination IP (host byte order). + * @param protocol IP protocol number. + * @param payload Packet payload. + * @param len Payload length. + * @return 0 on success, -1 on failure. + */ +int ipv4_send_iface(uint32_t iface_idx, uint32_t dst_ip, uint8_t protocol, + const void *payload, uint32_t len); + +/** + * Process an incoming IPv4 packet (called from Ethernet layer). + * + * @param data Raw IPv4 packet (header + payload). + * @param len Total packet length. + * @param iface_idx Interface index the packet arrived on. + */ +void ipv4_receive(const void *data, uint32_t len, uint32_t iface_idx); + +/** + * Register a protocol handler. + * + * @param protocol IP protocol number. + * @param handler Callback function. + * @return 0 on success, -1 if table is full. + */ +int ipv4_register_proto(uint8_t protocol, ipv4_proto_handler_t handler); + +/** + * Compute the Internet checksum over a buffer. + * + * @param data Buffer. + * @param len Length in bytes. + * @return Checksum in network byte order. + */ +uint16_t ipv4_checksum(const void *data, uint32_t len); + +/** + * Convert an IPv4 address string "A.B.C.D" to a uint32_t (host byte order). + * + * @param str Dotted-decimal string. + * @return IPv4 address, or 0 on failure. + */ +uint32_t ipv4_aton(const char *str); + +/** + * Format an IPv4 address (host byte order) into a dotted-decimal string. + * + * @param ip IPv4 address. + * @param buf Output buffer (at least 16 bytes). + * @param size Buffer size. + * @return Pointer to buf. + */ +char *ipv4_ntoa(uint32_t ip, char *buf, uint32_t size); + +#endif /* IPV4_H */ diff --git a/src/kernel.c b/src/kernel.c index 288953e..4b52843 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -23,6 +23,7 @@ #include "fat32.h" #include "keyboard.h" #include "ethernet.h" +#include "ipv4.h" #include "framebuffer.h" /* Global framebuffer info, parsed from multiboot2 tags. */ @@ -419,6 +420,9 @@ void kernel_main(uint32_t magic, uint32_t addr) { ethernet_init(); offset_print("Ethernet subsystem initialized\n"); + ipv4_init(); + offset_print("IPv4 stack initialized\n"); + init_drivers(); EARLY_PRINT("DRV "); offset_print("Drivers initialized\n");