From d7d7e8e58ec9881614468cfdcd6a1adc2a1240b0 Mon Sep 17 00:00:00 2001 From: AI Date: Tue, 24 Feb 2026 07:31:45 +0000 Subject: [PATCH] Implement ARP subsystem and arp app (AI) - Created src/arp.h: ARP packet struct, cache entry struct, operation codes, lookup/request/resolve/receive API, sysfs registration - Created src/arp.c: ARP cache with 32 entries, request/reply handling, ARP response to incoming requests for our IP, sysfs /sys/arp/table with formatted IP/MAC/interface/state columns - Created apps/arp/arp.c: reads and displays /sys/arp/table - Kernel calls arp_init() at boot, registered sysfs 'arp' namespace - Tested: clean boot, ARP initialized, arp app in CPIO --- README.md | 2 +- apps/arp/arp.c | 30 ++++ build.log | 14 +- src/CMakeLists.txt | 1 + src/arp.c | 382 +++++++++++++++++++++++++++++++++++++++++++++ src/arp.h | 145 +++++++++++++++++ src/kernel.c | 4 + 7 files changed, 571 insertions(+), 7 deletions(-) create mode 100644 apps/arp/arp.c create mode 100644 src/arp.c create mode 100644 src/arp.h diff --git a/README.md b/README.md index 0cc4171..d4d68d5 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ Once a task is completed, it should be checked off. - [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`. - [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` +- [x] 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. - [ ] Implement a simple version of `ftp` and `wget`. diff --git a/apps/arp/arp.c b/apps/arp/arp.c new file mode 100644 index 0000000..6321278 --- /dev/null +++ b/apps/arp/arp.c @@ -0,0 +1,30 @@ +/** + * @file arp.c + * @brief Display the ARP table. + * + * Reads the ARP cache from /sys/arp/table and displays it. + * + * Usage: + * arp - Show the ARP table + */ + +#include "syscalls.h" + +int main(void) { + /* Open the ARP table sysfs file */ + int32_t fd = open("/sys/arp/table", 0); + if (fd < 0) { + puts("arp: failed to open /sys/arp/table\n"); + return 1; + } + + /* Read and display contents */ + char buf[512]; + int32_t n; + while ((n = read(fd, buf, sizeof(buf))) > 0) { + write(1, buf, (uint32_t)n); + } + + close(fd); + return 0; +} diff --git a/build.log b/build.log index 6c5bbbe..ed5bee4 100644 --- a/build.log +++ b/build.log @@ -2,6 +2,8 @@ -- Generating done (0.2s) -- Build files have been written to: /workspaces/claude-os/build [ 2%] Building user-mode applications +Building app: arp + Built: /workspaces/claude-os/build/apps_bin/arp (214 bytes) Building app: cat Built: /workspaces/claude-os/build/apps_bin/cat (310 bytes) Building app: diskpart @@ -35,10 +37,10 @@ Building app: sh Built: /workspaces/claude-os/build/apps_bin/sh (3428 bytes) [ 2%] Built target apps [ 5%] Generating CPIO initial ramdisk -Generated initrd: 24100 bytes +Generated initrd: 24432 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 +[ 10%] Building C object src/CMakeFiles/kernel.dir/arp.c.o [ 13%] Linking C executable ../bin/kernel [ 97%] Built target kernel [100%] Generating bootable ISO image @@ -48,14 +50,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.JjepBC' +Added to ISO image: directory '/'='/tmp/grub.OdkOfh' 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 : 0.27% done -ISO image produced: 5974 sectors -Written to medium : 5974 sectors at LBA 0 +xorriso : UPDATE : 63.88% done +ISO image produced: 5986 sectors +Written to medium : 5986 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 ef22ea1..989e716 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -29,6 +29,7 @@ add_executable(kernel e3c509.c ethernet.c ipv4.c + arp.c env.c keyboard.c interrupts.S diff --git a/src/arp.c b/src/arp.c new file mode 100644 index 0000000..cec13de --- /dev/null +++ b/src/arp.c @@ -0,0 +1,382 @@ +/** + * @file arp.c + * @brief Address Resolution Protocol (ARP) implementation. + * + * Maintains an ARP cache mapping IPv4 addresses to Ethernet MAC + * addresses. Sends ARP requests and processes ARP replies. + * Also responds to incoming ARP requests for our own IP addresses. + * + * The ARP table is exposed via sysfs at /sys/arp. + */ + +#include "arp.h" +#include "ethernet.h" +#include "ipv4.h" +#include "sysfs.h" +#include + +/* Debug print helpers */ +extern void offset_print(const char *str); +extern void print_hex(uint32_t val); + +/* ================================================================ + * Global state + * ================================================================ */ + +/** ARP cache table. */ +static arp_entry_t arp_table[ARP_TABLE_SIZE]; + +/** Number of active entries. */ +static uint32_t arp_count = 0; + +/* ================================================================ + * ARP cache management + * ================================================================ */ + +/** + * Find an ARP entry by IP address. + * @return Pointer to entry, or NULL if not found. + */ +static arp_entry_t *arp_find(uint32_t ip) { + for (uint32_t i = 0; i < ARP_TABLE_SIZE; i++) { + if (arp_table[i].state != ARP_STATE_FREE && + arp_table[i].ip_addr == ip) { + return &arp_table[i]; + } + } + return NULL; +} + +/** + * Allocate a new ARP entry. + * Reuses a free slot, or evicts the oldest entry. + */ +static arp_entry_t *arp_alloc(void) { + /* Look for a free slot */ + for (uint32_t i = 0; i < ARP_TABLE_SIZE; i++) { + if (arp_table[i].state == ARP_STATE_FREE) { + return &arp_table[i]; + } + } + + /* Evict the oldest entry (lowest timestamp) */ + arp_entry_t *oldest = &arp_table[0]; + for (uint32_t i = 1; i < ARP_TABLE_SIZE; i++) { + if (arp_table[i].timestamp < oldest->timestamp) { + oldest = &arp_table[i]; + } + } + return oldest; +} + +/* ================================================================ + * ARP send + * ================================================================ */ + +/** + * Send an ARP packet. + */ +static int arp_send_packet(uint32_t iface_idx, uint16_t operation, + const uint8_t *target_mac, uint32_t target_ip) { + eth_iface_t *iface = ethernet_get_iface(iface_idx); + if (!iface || !iface->active) return -1; + + arp_packet_t pkt; + pkt.hw_type = htons(ARP_HW_ETHER); + pkt.proto_type = htons(ETHERTYPE_IPV4); + pkt.hw_len = 6; + pkt.proto_len = 4; + pkt.operation = htons(operation); + + /* Sender: our MAC and IP */ + memcpy(pkt.sender_mac, iface->mac, 6); + pkt.sender_ip = htonl(iface->ip_addr); + + /* Target */ + memcpy(pkt.target_mac, target_mac, 6); + pkt.target_ip = htonl(target_ip); + + /* For ARP requests, broadcast; for replies, send directly */ + const uint8_t *dst_mac; + if (operation == ARP_OP_REQUEST) { + static const uint8_t bcast[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + dst_mac = bcast; + } else { + dst_mac = target_mac; + } + + return ethernet_send(iface, dst_mac, ETHERTYPE_ARP, + &pkt, ARP_PACKET_SIZE); +} + +/* ================================================================ + * Public API + * ================================================================ */ + +int arp_lookup(uint32_t ip, uint8_t *mac) { + arp_entry_t *entry = arp_find(ip); + if (entry && entry->state == ARP_STATE_RESOLVED) { + memcpy(mac, entry->mac, 6); + return 0; + } + return -1; +} + +int arp_request(uint32_t iface_idx, uint32_t target_ip) { + static const uint8_t zero_mac[6] = {0, 0, 0, 0, 0, 0}; + + /* Create an incomplete entry if we don't have one */ + arp_entry_t *entry = arp_find(target_ip); + if (!entry) { + entry = arp_alloc(); + memset(entry, 0, sizeof(arp_entry_t)); + entry->ip_addr = target_ip; + entry->state = ARP_STATE_INCOMPLETE; + entry->iface_idx = (uint8_t)iface_idx; + entry->timestamp = 0; /* will be updated on reply */ + arp_count++; + } + + return arp_send_packet(iface_idx, ARP_OP_REQUEST, zero_mac, target_ip); +} + +int arp_resolve(uint32_t iface_idx, uint32_t ip, uint8_t *mac) { + /* Broadcast address — no ARP needed */ + if (ip == 0xFFFFFFFF) { + memset(mac, 0xFF, 6); + return 0; + } + + /* Check cache */ + if (arp_lookup(ip, mac) == 0) return 0; + + /* Send ARP request */ + arp_request(iface_idx, ip); + return -1; +} + +void arp_receive(const void *data, uint32_t len, uint32_t iface_idx) { + if (len < ARP_PACKET_SIZE) return; + + const arp_packet_t *pkt = (const arp_packet_t *)data; + + /* Validate: must be Ethernet/IPv4 */ + if (ntohs(pkt->hw_type) != ARP_HW_ETHER) return; + if (ntohs(pkt->proto_type) != ETHERTYPE_IPV4) return; + if (pkt->hw_len != 6 || pkt->proto_len != 4) return; + + uint32_t sender_ip = ntohl(pkt->sender_ip); + uint32_t target_ip = ntohl(pkt->target_ip); + uint16_t operation = ntohs(pkt->operation); + + /* Update or create ARP cache entry for sender */ + arp_entry_t *entry = arp_find(sender_ip); + if (entry) { + memcpy(entry->mac, pkt->sender_mac, 6); + entry->state = ARP_STATE_RESOLVED; + entry->iface_idx = (uint8_t)iface_idx; + } else { + entry = arp_alloc(); + entry->ip_addr = sender_ip; + memcpy(entry->mac, pkt->sender_mac, 6); + entry->state = ARP_STATE_RESOLVED; + entry->iface_idx = (uint8_t)iface_idx; + entry->timestamp = 0; + arp_count++; + } + + /* Check if this ARP is targeted at us */ + eth_iface_t *iface = ethernet_get_iface(iface_idx); + if (!iface || iface->ip_addr == 0) return; + + if (target_ip == iface->ip_addr && operation == ARP_OP_REQUEST) { + /* Send ARP reply */ + arp_send_packet(iface_idx, ARP_OP_REPLY, + pkt->sender_mac, sender_ip); + } +} + +int arp_add_static(uint32_t ip, const uint8_t *mac, uint32_t iface_idx) { + arp_entry_t *entry = arp_find(ip); + if (!entry) { + entry = arp_alloc(); + if (!entry) return -1; + arp_count++; + } + + entry->ip_addr = ip; + memcpy(entry->mac, mac, 6); + entry->state = ARP_STATE_RESOLVED; + entry->iface_idx = (uint8_t)iface_idx; + entry->timestamp = 0; + return 0; +} + +const arp_entry_t *arp_get_entry(uint32_t index) { + uint32_t count = 0; + for (uint32_t i = 0; i < ARP_TABLE_SIZE; i++) { + if (arp_table[i].state == ARP_STATE_FREE) continue; + if (count == index) return &arp_table[i]; + count++; + } + return NULL; +} + +uint32_t arp_get_count(void) { + return arp_count; +} + +/* ================================================================ + * Sysfs interface: /sys/arp + * + * Layout: + * /sys/arp/ + * table - ARP table in human-readable format + * ================================================================ */ + +/** + * Format a MAC address as "XX:XX:XX:XX:XX:XX". + */ +static void mac_to_str(const uint8_t *mac, char *buf) { + static const char hex[] = "0123456789ABCDEF"; + int pos = 0; + for (int i = 0; i < 6; i++) { + if (i > 0) buf[pos++] = ':'; + buf[pos++] = hex[(mac[i] >> 4) & 0xF]; + buf[pos++] = hex[mac[i] & 0xF]; + } + buf[pos] = '\0'; +} + +/** + * Format an IPv4 address (host byte order) as dotted decimal. + */ +static int ip_to_str(uint32_t ip, char *buf, uint32_t size) { + int pos = 0; + for (int i = 0; i < 4; i++) { + if (i > 0 && pos < (int)size - 1) buf[pos++] = '.'; + uint8_t octet = (uint8_t)((ip >> (24 - i * 8)) & 0xFF); + if (octet >= 100 && pos < (int)size - 3) { + buf[pos++] = (char)('0' + octet / 100); + buf[pos++] = (char)('0' + (octet % 100) / 10); + buf[pos++] = (char)('0' + octet % 10); + } else if (octet >= 10 && pos < (int)size - 2) { + buf[pos++] = (char)('0' + octet / 10); + buf[pos++] = (char)('0' + octet % 10); + } else if (pos < (int)size - 1) { + buf[pos++] = (char)('0' + octet); + } + } + if (pos < (int)size) buf[pos] = '\0'; + return pos; +} + +/** + * Append a string to buf at position *pos. + */ +static void append_str(char *buf, uint32_t size, int *pos, const char *s) { + while (*s && *pos < (int)size - 1) buf[(*pos)++] = *s++; +} + +static int arp_sysfs_list(void *ctx, const char *path, uint32_t idx, + sysfs_entry_t *out) { + (void)ctx; + + if (path[0] == '\0') { + if (idx == 0) { + memset(out, 0, sizeof(sysfs_entry_t)); + strncpy(out->name, "table", SYSFS_MAX_NAME - 1); + out->is_dir = 0; + return 0; + } + return -1; + } + return -1; +} + +static int arp_sysfs_read(void *ctx, const char *path, char *buf, + uint32_t buf_size) { + (void)ctx; + + if (strcmp(path, "table") != 0) return -1; + + int pos = 0; + + /* Header */ + append_str(buf, buf_size, &pos, "IP Address MAC Address Iface State\n"); + + for (uint32_t i = 0; i < ARP_TABLE_SIZE; i++) { + if (arp_table[i].state == ARP_STATE_FREE) continue; + + /* IP address */ + char ip_buf[16]; + ip_to_str(arp_table[i].ip_addr, ip_buf, sizeof(ip_buf)); + append_str(buf, buf_size, &pos, ip_buf); + + /* Pad to column 17 */ + int ip_len = (int)strlen(ip_buf); + for (int p = ip_len; p < 17; p++) { + if (pos < (int)buf_size - 1) buf[pos++] = ' '; + } + + /* MAC */ + char mac_buf[18]; + mac_to_str(arp_table[i].mac, mac_buf); + append_str(buf, buf_size, &pos, mac_buf); + if (pos < (int)buf_size - 1) buf[pos++] = ' '; + if (pos < (int)buf_size - 1) buf[pos++] = ' '; + + /* Interface */ + eth_iface_t *iface = ethernet_get_iface(arp_table[i].iface_idx); + if (iface) { + append_str(buf, buf_size, &pos, iface->name); + } else { + append_str(buf, buf_size, &pos, "?"); + } + + /* Pad and state */ + int name_len = iface ? (int)strlen(iface->name) : 1; + for (int p = name_len; p < 7; p++) { + if (pos < (int)buf_size - 1) buf[pos++] = ' '; + } + + if (arp_table[i].state == ARP_STATE_RESOLVED) { + append_str(buf, buf_size, &pos, "resolved"); + } else { + append_str(buf, buf_size, &pos, "incomplete"); + } + + if (pos < (int)buf_size - 1) buf[pos++] = '\n'; + } + + buf[pos] = '\0'; + return pos; +} + +static int arp_sysfs_write(void *ctx, const char *path, const char *buf, + uint32_t size) { + (void)ctx; + (void)path; + (void)buf; + (void)size; + return -1; /* Read-only for now */ +} + +static sysfs_ops_t arp_sysfs_ops = { + .list = arp_sysfs_list, + .read = arp_sysfs_read, + .write = arp_sysfs_write, +}; + +/* ================================================================ + * Initialization + * ================================================================ */ + +void arp_init(void) { + memset(arp_table, 0, sizeof(arp_table)); + arp_count = 0; + + sysfs_register("arp", &arp_sysfs_ops, NULL); + + offset_print(" ARP: initialized\n"); +} diff --git a/src/arp.h b/src/arp.h new file mode 100644 index 0000000..9fb038d --- /dev/null +++ b/src/arp.h @@ -0,0 +1,145 @@ +/** + * @file arp.h + * @brief Address Resolution Protocol (ARP) subsystem. + * + * Implements ARP (RFC 826) for mapping IPv4 addresses to Ethernet + * MAC addresses. Maintains an ARP cache and handles ARP requests + * and replies. + * + * The ARP table is exposed via sysfs at /sys/arp for userspace tools. + */ + +#ifndef ARP_H +#define ARP_H + +#include + +/** Maximum number of ARP cache entries. */ +#define ARP_TABLE_SIZE 32 + +/** ARP hardware type: Ethernet */ +#define ARP_HW_ETHER 1 + +/** ARP operation codes */ +#define ARP_OP_REQUEST 1 +#define ARP_OP_REPLY 2 + +/** ARP cache entry states */ +#define ARP_STATE_FREE 0 /**< Unused slot. */ +#define ARP_STATE_INCOMPLETE 1 /**< Request sent, awaiting reply. */ +#define ARP_STATE_RESOLVED 2 /**< MAC address known. */ + +/* ================================================================ + * ARP packet (28 bytes for IPv4-over-Ethernet) + * ================================================================ */ + +/** + * ARP packet structure for IPv4 over Ethernet. + * All multi-byte fields are in network byte order. + */ +typedef struct __attribute__((packed)) arp_packet { + uint16_t hw_type; /**< Hardware type (1 = Ethernet). */ + uint16_t proto_type; /**< Protocol type (0x0800 = IPv4). */ + uint8_t hw_len; /**< Hardware address length (6). */ + uint8_t proto_len; /**< Protocol address length (4). */ + uint16_t operation; /**< Operation (1=request, 2=reply). */ + uint8_t sender_mac[6]; /**< Sender hardware address. */ + uint32_t sender_ip; /**< Sender protocol address. */ + uint8_t target_mac[6]; /**< Target hardware address. */ + uint32_t target_ip; /**< Target protocol address. */ +} arp_packet_t; + +/** ARP packet size. */ +#define ARP_PACKET_SIZE 28 + +/* ================================================================ + * ARP cache entry + * ================================================================ */ + +/** + * ARP cache entry. + */ +typedef struct arp_entry { + uint32_t ip_addr; /**< IPv4 address (host byte order). */ + uint8_t mac[6]; /**< Resolved MAC address. */ + uint8_t state; /**< ARP_STATE_*. */ + uint8_t iface_idx; /**< Ethernet interface index. */ + uint32_t timestamp; /**< Time when entry was created (tick count). */ +} arp_entry_t; + +/* ================================================================ + * Public API + * ================================================================ */ + +/** + * Initialize the ARP subsystem. + * Registers sysfs namespace "arp". + */ +void arp_init(void); + +/** + * Look up an IP address in the ARP cache. + * + * @param ip IPv4 address (host byte order). + * @param mac Output: 6-byte MAC address if found. + * @return 0 if found and resolved, -1 if not in cache or incomplete. + */ +int arp_lookup(uint32_t ip, uint8_t *mac); + +/** + * Send an ARP request for the given IP address. + * + * @param iface_idx Ethernet interface to send on. + * @param target_ip IP address to resolve (host byte order). + * @return 0 on success, -1 on failure. + */ +int arp_request(uint32_t iface_idx, uint32_t target_ip); + +/** + * Resolve an IP address to a MAC address. + * + * Checks the ARP cache first. If not found, sends an ARP request + * and returns -1 (caller should retry later). + * + * @param iface_idx Ethernet interface index. + * @param ip IPv4 address (host byte order). + * @param mac Output: 6-byte MAC address. + * @return 0 if resolved, -1 if pending. + */ +int arp_resolve(uint32_t iface_idx, uint32_t ip, uint8_t *mac); + +/** + * Process an incoming ARP packet. + * + * Called by the Ethernet subsystem when an ARP frame is received. + * + * @param data Raw ARP packet. + * @param len Packet length. + * @param iface_idx Interface the packet arrived on. + */ +void arp_receive(const void *data, uint32_t len, uint32_t iface_idx); + +/** + * Add a static ARP entry. + * + * @param ip IPv4 address (host byte order). + * @param mac 6-byte MAC address. + * @param iface_idx Ethernet interface index. + * @return 0 on success, -1 if table full. + */ +int arp_add_static(uint32_t ip, const uint8_t *mac, uint32_t iface_idx); + +/** + * Get an ARP table entry by index (for enumeration). + * + * @param index 0-based index. + * @return Pointer to entry, or NULL if out of range. + */ +const arp_entry_t *arp_get_entry(uint32_t index); + +/** + * Get the number of active ARP entries. + */ +uint32_t arp_get_count(void); + +#endif /* ARP_H */ diff --git a/src/kernel.c b/src/kernel.c index 4b52843..c9057b6 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -24,6 +24,7 @@ #include "keyboard.h" #include "ethernet.h" #include "ipv4.h" +#include "arp.h" #include "framebuffer.h" /* Global framebuffer info, parsed from multiboot2 tags. */ @@ -423,6 +424,9 @@ void kernel_main(uint32_t magic, uint32_t addr) { ipv4_init(); offset_print("IPv4 stack initialized\n"); + arp_init(); + offset_print("ARP subsystem initialized\n"); + init_drivers(); EARLY_PRINT("DRV "); offset_print("Drivers initialized\n");