Files
microtcp/src/arp.c
T
2023-03-20 09:43:11 +01:00

734 lines
25 KiB
C

/* Il protocollo ARP (Address Resolution Protocol)
* permette di tradurre gli indirizzi di livello
* rete (come IP) a quelli del livello inferiore
* data-link (come ethernet).
* Per grandi linee, i messaggi ARP possono essere
* REQUEST o REPLY. Quando un host vuole comunicare
* con un altro host dato il suo IP (o qualsiasi
* indirizzo di livello 3), manda un messaggio di
* REQUEST in broadcast (MAC di destinazione
* ff:ff:ff:ff:ff:ff) contenente l'indirizzo di
* livello 3 del quale si vuole conoscere quello di
* livello 2. Ciascun host della rete riceve il
* messaggio e controlla se la richiesta è relativa
* al proprio IP. Se il controllo risulta positivo
* risponde con un messaggio di REPLY contenente
* il proprio MAC. Il messaggio di REPLY, a differenza
* della REQUEST è un unicast dato che è noto il
* destinatario.
*
* Il messaggio ARP è grande 28 byte ed, indipendentemente
* dal tipo di richiesta, ha questa struttura:
*
* (16 bits per row)
* +-----------------------------------+
* 0 | hardware_type |
* +-----------------------------------+
* 2 | protocol_type |
* +-----------------+-----------------+
* 4 | hardware_length | protocol_length |
* +-----------------+-----------------+
* 6 | operation_type |
* +-----------------------------------+
* 8 | sender_hardware_address |
* +- - - - - - - - - - - - - - - - - -+
* | |
* +- - - - - - - - - - - - - - - - - -+
* | |
* +-----------------------------------+
* 14 | sender_protocol_address |
* +- - - - - - - - - - - - - - - - - -+
* | |
* +-----------------------------------+
* 18 | target_hardware_address |
* +- - - - - - - - - - - - - - - - - -+
* | |
* +- - - - - - - - - - - - - - - - - -+
* | |
* +-----------------------------------+
* 24 | target_protocol_address |
* +- - - - - - - - - - - - - - - - - -+
* | |
* +-----------------------------------+
*
* I campi hardware_type e protocol_type indicano
* il protocollo di livello 2 e quello di livello 3.
* Nel caso di IP su Ethernet si ha hardware_type=1
* e protocol_type=0x800.
*
* I campi hardware_length e protocol_length
* indicano la dimensione in byte degli indirizzi
* dei due protocolli. Per IP e Ethernet questi
* sono ridondanti dato che ogni indirizzo Ethernet
* è di 6 byte ed ogni indirizzo IP di 4.
*
* Il campo operation_type indica la finalità del
* messaggio. Può essere uno di:
* ARP REQUEST -> operation_type=1
* ARP REPLY -> operation_type=2
* RARP REQUEST -> operation_type=3
* RARP REPLY -> operation_type=4
* (possiamo ignorare RARP per ora)
*
* Il campo sender_hardware_address e sender_protocol_address
* contengono MAC e IP di chi ha inviato il messaggio.
*
* Il campo target_hardware_address cambia di significato
* a seconda del tipo di operazione:
* ARP REQUEST -> è vuoto, perchè non è noto il MAC di
* destinazione (vogliamo determinarlo)
* ARP REPLY -> indirizzo MAC di chi ha fatto la REQUEST
*
* Il campo target_protocol_address contiene l'indirizzo IP
* di chi ha inviato il messaggio.
*/
/*
struct iphdr
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
unsigned int ihl:4;
unsigned int version:4;
#elif __BYTE_ORDER == __BIG_ENDIAN
unsigned int version:4;
unsigned int ihl:4;
#else
# error "Please fix <bits/endian.h>"
#endif
uint8_t tos;
uint16_t tot_len;
uint16_t id;
uint16_t frag_off;
uint8_t ttl;
uint8_t protocol;
uint16_t check;
uint32_t saddr;
uint32_t daddr;
// The options start here.
};
struct ethhdr {
unsigned char h_dest[ETH_ALEN]; // destination eth addr
unsigned char h_source[ETH_ALEN]; // source ether addr
__be16 h_proto; // packet type ID field
} __attribute__((packed));
*/
#include <stdbool.h>
#include <arpa/inet.h>
#include "arp.h"
#ifdef ARP_DEBUG
#include <stdio.h>
#define ARP_DEBUG_LOG(fmt, ...) fprintf(stderr, "ARP :: " fmt "\n", ## __VA_ARGS__)
#else
#define ARP_DEBUG_LOG(...)
#endif
typedef enum {
ARP_HARDWARE_ETHERNET = 1,
} arp_hardware_type;
typedef enum {
ARP_PROTOCOL_IP = 0x800,
} arp_protocol_type;
typedef enum {
ARP_OPERATION_REQUEST = 1,
ARP_OPERATION_REPLY = 2,
} arp_operation_t;
void arp_change_output_buffer(arp_state_t *state, void *ptr, size_t max)
{
if (max < sizeof(arp_packet_t))
state->output = NULL;
else
state->output = ptr;
}
static void arp_translation_table_seconds_passed(arp_translation_table_t *table, size_t seconds)
{
table->time += seconds;
// Determine all of the elements of the table that have just
// timed out.
//
// The [used_list] contains all of the active table entries
// in a doubly linked list. The first element is referred by
// [table->used_list_head], and the last by [table->used_list_tail].
// The entries are ordered in descending [entry->timeout]
// attribute. The [timeout] attribute indicates the absolute
// time at which the entry will be considered invalid,
// relative to [table->time].
//
// Since the list goes from high to low timeout, if an entry
// at a given point in time isn't timed-out, all of the
// entries that come before it also aren't timed-out.
// Analogously, is an entry in a given point in time is
// timed-out, all of the entries after it are also timed-out.
//
// In general, at any given point in time, the list is made
// of a first half of non-timed-out entries and a second half
// of timed-out entries.
//
// This function needs to remove the timed-out tail of the
// used entries list and add it to the free entry list.
//
// Find from the end of the list the first non-timed-out
// entry. The timed-out elements will be all of the ones
// that come after it.
//
// NOTE: If all of the entries are timed-out or the list is
// empty, the loop will exit with the NULL entry.
arp_translation_table_entry_t *entry = table->used_list_tail;
while (entry && entry->timeout < table->time)
entry = entry->prev;
// First and last element of the timed-out list. We need
// to determine these.
arp_translation_table_entry_t *timeout_list;
arp_translation_table_entry_t *timeout_tail;
if (entry) {
// The iteration didn't end with a NULL cursor, so either
// there are no timed-out elements (in which case the cursor
// is the tail of the list) or there are both timed-out and
// non-timed-out entries.
//
// Either way, the start of the list is [entry->next].
timeout_list = entry->next;
//
// If there are no timed-out entries, the tail of the timed-out
// list must be NULL, else it's the tail of the used list.
timeout_tail = entry->next ? table->used_list_tail : NULL;
//
// The entry becomes the new tail
entry->next = NULL;
table->used_list_tail = entry;
} else {
// If the iteration ended with a NULL cursor, there
// are no valid entries in the list. Either the list
// is all timed-out, or it's empty.
//
// Either way we take the list pointers and make them
// out timed-out list.
timeout_list = table->used_list_head;
timeout_tail = table->used_list_tail;
//
// If the list wasn't empty, we make it so.
table->used_list_head = NULL;
table->used_list_tail = NULL;
}
// Append the timed-out list to the free list
if (timeout_list) {
timeout_list->prev = NULL;
timeout_tail->next = table->free_list;
table->free_list = timeout_list;
}
}
void arp_seconds_passed(arp_state_t *state, size_t seconds)
{
state->time += seconds;
// Scan through all of the timed-out entries
// in the pending request list from the tail
arp_pending_request_t *cursor = state->pending_request_used_tail;
while (cursor && cursor->timeout < state->time)
cursor = cursor->prev;
// Chop off the list of timed out entries
arp_pending_request_t *timeout_list;
arp_pending_request_t *timeout_tail;
if (cursor) {
// Cursor holds the first request that's not timed out,
// therefore all of the entries that come after it are
// now invalid
timeout_list = cursor->next;
timeout_tail = cursor->next ? state->pending_request_used_tail : NULL;
// Now chop off the list
cursor->next = NULL;
state->pending_request_used_tail = cursor;
} else {
// Either the list is empty or all of the requests are
// now invalid.
timeout_list = state->pending_request_used_list;
timeout_tail = state->pending_request_used_tail;
state->pending_request_used_list = NULL;
state->pending_request_used_tail = NULL;
}
// Now walk through the timed out entries and
// run the callback with the timeout status code
arp_pending_request_t *timeout_cursor = timeout_list;
while (timeout_cursor) {
timeout_cursor->callback(timeout_cursor->callback_data, ARP_RESOLUTION_TIMEOUT, MAC_ZERO);
timeout_cursor = timeout_cursor->next;
}
// Now put the timed out entries back in the free
// list (if there are any)
if (timeout_list) {
timeout_list->prev = NULL;
timeout_tail->next = state->pending_request_free_list;
state->pending_request_free_list = timeout_list;
}
arp_translation_table_seconds_passed(&state->table, seconds);
}
static void
arp_translation_table_init(arp_translation_table_t *table)
{
table->time = 0;
table->used_list_head = NULL;
table->used_list_tail = NULL;
table->free_list = table->entries;
for (size_t i = 0; i < ARP_TRANSLATION_TABLE_SIZE-1; i++) {
table->entries[i].prev = NULL;
table->entries[i].next = table->entries + i+1;
}
table->entries[ARP_TRANSLATION_TABLE_SIZE-1].prev = NULL;
table->entries[ARP_TRANSLATION_TABLE_SIZE-1].next = NULL;
}
static void
arp_translation_table_free(arp_translation_table_t *table)
{
(void) table;
}
#ifdef ARP_DEBUG
static bool
arp_translation_table_entry_is_used(arp_translation_table_t *table,
arp_translation_table_entry_t *entry)
{
arp_translation_table_entry_t *cursor = table->used_list_head;
while (cursor) {
if (cursor == entry)
return true;
cursor = cursor->next;
}
return false;
}
static bool
arp_translation_table_entry_is_unlinked(arp_translation_table_t *table,
arp_translation_table_entry_t *entry)
{
return entry->prev == NULL
&& entry->next == NULL
&& table->free_list != entry
&& table->used_list_head != entry
&& table->used_list_tail != entry;
}
#endif
static void
arp_translation_table_unlink_used_entry(arp_translation_table_t *table,
arp_translation_table_entry_t *entry)
{
#ifdef ARP_DEBUG
assert(!arp_translation_table_entry_is_unlinked(table, entry));
#endif
if (entry->prev)
entry->prev->next = entry->next;
else
table->used_list_head = entry->next;
if (entry->next)
entry->next->prev = entry->prev;
else
table->used_list_tail = entry->prev;
entry->prev = NULL;
entry->next = NULL;
#ifdef ARP_DEBUG
assert(arp_translation_table_entry_is_unlinked(table, entry));
#endif
}
static void
arp_translation_table_insert_unlinked_entry_into_used_list(arp_translation_table_t *table,
arp_translation_table_entry_t *entry)
{
#ifdef ARP_DEBUG
assert(arp_translation_table_entry_is_unlinked(table, entry));
assert(!arp_translation_table_entry_is_used(table, entry));
#endif
// Find the first entry with the lower timeout
arp_translation_table_entry_t *cursor = table->used_list_head;
while (cursor && cursor->timeout < entry->timeout)
cursor = cursor->next;
if (cursor) {
// Insert the entry before the cursor position.
entry->prev = cursor->prev;
entry->next = cursor;
if (cursor->prev)
cursor->prev->next = entry;
else
table->used_list_head = entry;
cursor->prev = entry;
} else {
// Either the list is empty or the entry must
// be inserted last.
entry->prev = table->used_list_tail;
entry->next = NULL;
if (table->used_list_tail)
table->used_list_tail->next = entry;
else
table->used_list_head = entry;
table->used_list_tail = entry;
}
#ifdef ARP_DEBUG
assert(!arp_translation_table_entry_is_unlinked(table, entry));
assert(arp_translation_table_entry_is_used(table, entry));
#endif
}
static void
arp_translation_table_free_least_recently_used_entry(arp_translation_table_t *table)
{
arp_translation_table_entry_t *entry = table->used_list_tail;
if (entry) {
#ifdef ARP_DEBUG
assert(!arp_translation_table_entry_is_unlinked(table, entry));
#endif
arp_translation_table_unlink_used_entry(table, entry);
#ifdef ARP_DEBUG
assert(arp_translation_table_entry_is_unlinked(table, entry));
#endif
// Push the entry to the free list
entry->next = table->free_list;
table->free_list = entry;
#ifdef ARP_DEBUG
assert(!arp_translation_table_entry_is_unlinked(table, entry));
#endif
}
}
static arp_translation_table_entry_t*
arp_translation_table_find_entry_by_ip(arp_translation_table_t *table,
ip_address_t ip)
{
arp_translation_table_entry_t *entry = table->used_list_head;
while (entry) {
if (entry->ip == ip)
return entry;
entry = entry->next;
}
return NULL;
}
static bool arp_translation_table_find_mac_by_ip(arp_translation_table_t *table,
ip_address_t ip, mac_address_t *mac)
{
arp_translation_table_entry_t *entry =
arp_translation_table_find_entry_by_ip(table, ip);
if (entry)
*mac = entry->mac;
return !!entry;
}
static arp_translation_table_entry_t*
arp_translation_table_pop_free_entry(arp_translation_table_t *table)
{
arp_translation_table_entry_t *entry = table->free_list;
if (entry)
table->free_list = entry->next;
return entry;
}
static void
arp_translation_table_initialize_entry(arp_translation_table_entry_t *entry,
mac_address_t mac, ip_address_t ip,
uint64_t timeout)
{
entry->mac = mac;
entry->ip = ip;
entry->timeout = timeout;
entry->prev = NULL;
entry->next = NULL;
}
static void
arp_translation_table_insert_or_update(arp_translation_table_t *table,
mac_address_t mac, ip_address_t ip,
uint64_t timeout)
{
arp_translation_table_entry_t *entry =
arp_translation_table_find_entry_by_ip(table, ip);
if (entry) {
entry->timeout = table->time + timeout; // Refresh timeout
arp_translation_table_unlink_used_entry(table, entry);
} else {
entry = arp_translation_table_pop_free_entry(table);
if (!entry) {
arp_translation_table_free_least_recently_used_entry(table);
entry = arp_translation_table_pop_free_entry(table);
}
assert(entry);
arp_translation_table_initialize_entry(entry, mac, ip, table->time + timeout);
}
arp_translation_table_insert_unlinked_entry_into_used_list(table, entry);
}
static bool
arp_translation_table_update(arp_translation_table_t *table,
mac_address_t mac, ip_address_t ip,
uint64_t timeout)
{
arp_translation_table_entry_t *entry =
arp_translation_table_find_entry_by_ip(table, ip);
if (entry) {
arp_translation_table_unlink_used_entry(table, entry);
arp_translation_table_initialize_entry(entry, mac, ip, table->time + timeout);
arp_translation_table_insert_unlinked_entry_into_used_list(table, entry);
}
return !!entry;
}
void arp_init(arp_state_t *state, ip_address_t ip, mac_address_t mac,
void *send_data, void (*send)(void*, mac_address_t))
{
state->time = 0;
state->request_timeout = 1;
state->cache_timeout = 10;
state->output = NULL;
state->send_data = send_data;
state->send = send;
state->self_ip = ip;
state->self_mac = mac;
arp_translation_table_init(&state->table);
state->pending_request_used_list = NULL;
state->pending_request_used_tail = NULL;
state->pending_request_free_list = state->pending_request_pool;
for (size_t i = 0; i < ARP_MAX_PENDING_REQUESTS; i++)
state->pending_request_pool[i].next = state->pending_request_pool + i+1;
state->pending_request_pool[ARP_MAX_PENDING_REQUESTS-1].next = NULL;
}
void arp_free(arp_state_t *state)
{
arp_translation_table_free(&state->table);
}
static void append_pending_request_to_used_list(arp_state_t *state, arp_pending_request_t *pending_request)
{
arp_pending_request_t *cursor = state->pending_request_used_list;
// Find the first pending request in the list
// with a lower timeout and insert the request
// before it.
while (cursor && cursor->timeout > pending_request->timeout)
cursor = cursor->next;
if (cursor) {
pending_request->prev = cursor->prev;
pending_request->next = cursor;
if (cursor->prev)
cursor->prev->next = pending_request;
else
state->pending_request_used_list = pending_request;
cursor->prev = pending_request;
} else {
// Insert the request in the tail of the list
pending_request->prev = state->pending_request_used_tail;
pending_request->next = NULL;
if (state->pending_request_used_tail)
state->pending_request_used_tail->next = pending_request;
else
state->pending_request_used_list = pending_request;
state->pending_request_used_tail = pending_request;
}
}
void arp_resolve_mac(arp_state_t *state, ip_address_t ip, void *userp, void (*callback)(void*, arp_resolution_status_t, mac_address_t))
{
bool found_mac_locally;
mac_address_t mac;
if (state->self_ip == ip) {
mac = state->self_mac;
found_mac_locally = true;
} else
found_mac_locally = arp_translation_table_find_mac_by_ip(&state->table, ip, &mac);
if (found_mac_locally)
callback(userp, ARP_RESOLUTION_OK, mac);
else {
// MAC isn't in the translation table.
// We need to make an ARP REQUEST
arp_pending_request_t *pending_request = state->pending_request_free_list;
if (pending_request == NULL) {
callback(userp, ARP_RESOLUTION_FAILED, MAC_ZERO);
return;
}
state->pending_request_free_list = pending_request->next;
pending_request->ip = ip;
pending_request->timeout = state->time + state->request_timeout;
pending_request->callback = callback;
pending_request->callback_data = userp;
pending_request->prev = NULL;
pending_request->next = NULL;
append_pending_request_to_used_list(state, pending_request);
arp_packet_t *packet = state->output;
packet->hardware_type = htons(ARP_HARDWARE_ETHERNET);
packet->protocol_type = htons(ARP_PROTOCOL_IP);
packet->hardware_len = 6;
packet->protocol_len = 4;
packet->operation_type = htons(ARP_OPERATION_REQUEST);
packet->sender_hardware_address = state->self_mac;
packet->sender_protocol_address = state->self_ip;
packet->target_hardware_address = MAC_ZERO; // This is what we're trying to find
packet->target_protocol_address = ip;
ARP_DEBUG_LOG("Sending out ARP request to resolve MAC");
state->send(state->send_data, MAC_BROADCAST);
}
}
static void
try_resolving_pending_requests(arp_state_t *state, ip_address_t ip, mac_address_t mac)
{
// NOTE: Could try resolving pending requests from
// the tail of the list instead of the head
// since the tail entries have been waiting
// longer. I think we can assume the older
// entries have higher chances of being resolved.
arp_pending_request_t *pending_request = state->pending_request_used_list;
arp_pending_request_t *prev = NULL;
while (pending_request) {
arp_pending_request_t *next = pending_request->next;
if (pending_request->ip == ip) {
pending_request->callback(pending_request->callback_data, ARP_RESOLUTION_OK, mac);
pending_request->next = state->pending_request_free_list;
state->pending_request_free_list = pending_request;
if (prev)
prev->next = next;
else
state->pending_request_used_list = next;
if (next)
next->prev = prev;
else
state->pending_request_used_tail = prev;
} else
prev = pending_request;
pending_request = next;
}
}
arp_process_result_t arp_process_packet(arp_state_t *state, const void *packet, size_t len)
{
if (len != sizeof(arp_packet_t))
return ARP_PROCESS_RESULT_INVALID;
const arp_packet_t *packet2 = packet;
if (packet2->hardware_type != htons(ARP_HARDWARE_ETHERNET)) {
/* Level 2 protocol not supported */
ARP_DEBUG_LOG("Hardware type %d not supported", packet2->hardware_type);
return ARP_PROCESS_RESULT_HWARENOTSUPP;
}
if (packet2->protocol_type != htons(ARP_PROTOCOL_IP)) {
/* Level 3 protocol not supported */
ARP_DEBUG_LOG("Protocol type %d not supported", packet2->protocol_type);
return ARP_PROCESS_RESULT_PROTONOTSUPP;
}
if (packet2->hardware_len != 6 || packet2->protocol_len != 4) {
/* Invalid fields */
ARP_DEBUG_LOG("Invalid hardware or protocol address size %d or %d (expected %d and %d)", packet2->hardware_len, packet2->protocol_len, 6, 4);
return ARP_PROCESS_RESULT_INVALID;
}
bool merge = arp_translation_table_update(&state->table, packet2->sender_hardware_address,
packet2->sender_protocol_address, state->cache_timeout);
if (packet2->target_protocol_address == state->self_ip) {
if (!merge) {
arp_translation_table_insert_or_update(&state->table, packet2->sender_hardware_address,
packet2->sender_protocol_address, state->cache_timeout);
try_resolving_pending_requests(state, packet2->sender_protocol_address,
packet2->sender_hardware_address);
}
if (packet2->operation_type == htons(ARP_OPERATION_REQUEST)) {
// Generate the ARP REPLY
arp_packet_t *response = state->output;
response->hardware_type = packet2->hardware_type;
response->protocol_type = packet2->protocol_type;
response->hardware_len = packet2->hardware_len;
response->protocol_len = packet2->protocol_len;
response->operation_type = htons(ARP_OPERATION_REPLY);
response->sender_hardware_address = state->self_mac;
response->sender_protocol_address = state->self_ip;
response->target_hardware_address = packet2->sender_hardware_address;
response->target_protocol_address = packet2->sender_protocol_address;
ARP_DEBUG_LOG("Sending reply");
state->send(state->send_data, packet2->sender_hardware_address);
}
} else {
ARP_DEBUG_LOG("Request not for me");
}
return ARP_PROCESS_RESULT_OK;
}