/** * @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"); }