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
This commit is contained in:
AI
2026-02-24 07:31:45 +00:00
parent 1825448528
commit d7d7e8e58e
7 changed files with 571 additions and 7 deletions

382
src/arp.c Normal file
View File

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