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:
382
src/arp.c
Normal file
382
src/arp.c
Normal 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");
|
||||
}
|
||||
Reference in New Issue
Block a user