first commit

This commit is contained in:
Francesco Cozzuto
2023-03-20 09:43:11 +01:00
commit 00e68532d1
22 changed files with 3401 additions and 0 deletions
+734
View File
@@ -0,0 +1,734 @@
/* 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;
}
+98
View File
@@ -0,0 +1,98 @@
#include <assert.h>
#include <stdint.h>
#include <stddef.h>
#include "defs.h"
#define ARP_MAX_PENDING_REQUESTS 32
#define ARP_TRANSLATION_TABLE_SIZE 128
typedef enum {
ARP_RESOLUTION_OK,
ARP_RESOLUTION_FAILED,
ARP_RESOLUTION_TIMEOUT,
} arp_resolution_status_t;
typedef struct arp_translation_table_entry_t arp_translation_table_entry_t;
struct arp_translation_table_entry_t {
arp_translation_table_entry_t *prev;
arp_translation_table_entry_t *next;
mac_address_t mac;
ip_address_t ip;
uint64_t timeout;
};
typedef struct {
uint64_t time;
arp_translation_table_entry_t *used_list_head;
arp_translation_table_entry_t *used_list_tail;
arp_translation_table_entry_t *free_list;
arp_translation_table_entry_t entries[ARP_TRANSLATION_TABLE_SIZE];
} arp_translation_table_t;
typedef struct arp_pending_request_t arp_pending_request_t;
struct arp_pending_request_t {
arp_pending_request_t *prev;
arp_pending_request_t *next;
ip_address_t ip;
uint64_t timeout;
void *callback_data;
void (*callback)(void*, arp_resolution_status_t status, mac_address_t);
};
typedef struct __attribute__((__packed__)) {
uint16_t hardware_type;
uint16_t protocol_type;
uint8_t hardware_len;
uint8_t protocol_len;
uint16_t operation_type;
mac_address_t sender_hardware_address;
ip_address_t sender_protocol_address;
mac_address_t target_hardware_address;
ip_address_t target_protocol_address;
} arp_packet_t;
static_assert(offsetof(arp_packet_t, hardware_type) == 0);
static_assert(offsetof(arp_packet_t, protocol_type) == 2);
static_assert(offsetof(arp_packet_t, hardware_len) == 4);
static_assert(offsetof(arp_packet_t, protocol_len) == 5);
static_assert(offsetof(arp_packet_t, operation_type) == 6);
static_assert(offsetof(arp_packet_t, sender_hardware_address) == 8);
static_assert(offsetof(arp_packet_t, sender_protocol_address) == 14);
static_assert(offsetof(arp_packet_t, target_hardware_address) == 18);
static_assert(offsetof(arp_packet_t, target_protocol_address) == 24);
static_assert(sizeof(arp_packet_t) == 28);
typedef struct {
uint64_t time;
uint64_t cache_timeout;
uint64_t request_timeout;
arp_packet_t *output;
void *send_data;
void (*send)(void *send_data, mac_address_t dest_mac);
ip_address_t self_ip;
mac_address_t self_mac;
arp_translation_table_t table;
arp_pending_request_t *pending_request_free_list;
arp_pending_request_t *pending_request_used_list;
arp_pending_request_t *pending_request_used_tail;
arp_pending_request_t pending_request_pool[ARP_MAX_PENDING_REQUESTS];
} arp_state_t;
typedef enum {
ARP_PROCESS_RESULT_HWARENOTSUPP,
ARP_PROCESS_RESULT_PROTONOTSUPP,
ARP_PROCESS_RESULT_INVALID,
ARP_PROCESS_RESULT_OK,
} arp_process_result_t;
void arp_init(arp_state_t *state, ip_address_t ip, mac_address_t mac, void *send_data, void (*send)(void*, mac_address_t));
void arp_free(arp_state_t *state);
arp_process_result_t arp_process_packet(arp_state_t *state, const void *packet, size_t len);
void arp_resolve_mac(arp_state_t *state, ip_address_t ip, void *userp, void (*callback)(void*, arp_resolution_status_t, mac_address_t));
void arp_seconds_passed(arp_state_t *state, size_t seconds);
void arp_change_output_buffer(arp_state_t *state, void *ptr, size_t max);
+20
View File
@@ -0,0 +1,20 @@
#ifndef MICROTCP_DEFS_H
#define MICROTCP_DEFS_H
#include <stdint.h>
#include <assert.h> // static_assert
typedef struct {
uint8_t data[6];
} mac_address_t;
typedef uint32_t ip_address_t;
static_assert(sizeof(mac_address_t) == 6);
static_assert(sizeof(ip_address_t) == 4);
#define MAC_ZERO (mac_address_t) {.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}
#define MAC_BROADCAST (mac_address_t) {.data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
#endif /* MICROTCP_DEFS_H */
+113
View File
@@ -0,0 +1,113 @@
#include <string.h>
#include <arpa/inet.h>
#include "icmp.h"
#ifdef ICMP_DEBUG
#include <stdio.h>
#define ICMP_DEBUG_LOG(fmt, ...) fprintf(stderr, "ICMP :: " fmt "\n", ## __VA_ARGS__)
#else
#define ICMP_DEBUG_LOG(...)
#endif
typedef enum {
ICMP_TYPE_ECHO_REPLY = 0,
ICMP_TYPE_ECHO_REQUEST = 8,
} icmp_type_t;
typedef struct {
uint8_t type;
uint8_t code;
uint16_t checksum;
uint16_t id_no;
uint16_t seq_no;
uint8_t data[];
} icmp_message_echo_t;
typedef struct {
uint8_t type;
uint8_t code;
uint16_t checksum;
uint8_t data[];
} icmp_message_generic_t;
void icmp_change_output_buffer(icmp_state_t *state, void *ptr, size_t len)
{
state->output_ptr = ptr;
state->output_len = len;
}
void icmp_init(icmp_state_t *state, void *send_data, void (*send)(void*, ip_address_t, size_t))
{
state->output_ptr = NULL;
state->output_len = 0;
state->send_data = send_data;
state->send = send;
}
void icmp_free(icmp_state_t *state)
{
(void) state;
}
static uint16_t calculate_checksum_icmp(const void *src, size_t len)
{
assert((len & 1) == 0);
const uint16_t *src2 = src;
uint32_t sum = 0xffff;
for (size_t i = 0; i < len/2; i++) {
sum += ntohs(src2[i]);
if (sum > 0xffff)
sum -= 0xffff;
}
return htons(~sum);
}
void icmp_process_packet(icmp_state_t *state, ip_address_t ip, const void *src, size_t len)
{
if (len < sizeof(icmp_message_generic_t))
return;
const icmp_message_generic_t *packet = src;
switch (packet->type) {
case ICMP_TYPE_ECHO_REQUEST:
{
if (len < sizeof(icmp_message_echo_t))
return;
const icmp_message_echo_t *echo_request = (icmp_message_echo_t*) packet;
if (calculate_checksum_icmp(echo_request, len)) {
ICMP_DEBUG_LOG("Dropping ICMP message with invalid checksum");
return;
}
if (state->output_ptr == NULL || state->output_len < len) {
ICMP_DEBUG_LOG("Ignoring ECHO REQUEST because the output buffer is too small for an ECHO REPLY (have %ld, need %ld)", state->output_len, len);
return;
}
icmp_message_echo_t *echo_reply = state->output_ptr;
echo_reply->type = ICMP_TYPE_ECHO_REPLY;
echo_reply->code = 0;
echo_reply->checksum = 0;
echo_reply->id_no = echo_request->id_no;
echo_reply->seq_no = echo_request->seq_no;
memcpy(echo_reply->data, echo_request->data, len - sizeof(icmp_message_echo_t));
echo_reply->checksum = calculate_checksum_icmp(echo_reply, len);
ICMP_DEBUG_LOG("Replying to echo request");
state->send(state->send_data, ip, len);
}
break;
default:
// Unsupported ICMP message
break;
}
}
+14
View File
@@ -0,0 +1,14 @@
#include <stddef.h>
#include "defs.h"
typedef struct {
void *output_ptr;
size_t output_len;
void *send_data;
void (*send)(void *send_data, ip_address_t ip, size_t len);
} icmp_state_t;
void icmp_init(icmp_state_t *state, void *send_data, void (*send)(void*, ip_address_t, size_t));
void icmp_free(icmp_state_t *state);
void icmp_process_packet(icmp_state_t *state, ip_address_t ip, const void *src, size_t len);
void icmp_change_output_buffer(icmp_state_t *state, void *ptr, size_t len);
+221
View File
@@ -0,0 +1,221 @@
#include <string.h>
#include <arpa/inet.h> // ntohs()
#include "ip.h"
#ifdef IP_DEBUG
#include <stdio.h>
#define IP_DEBUG_LOG(fmt, ...) do { fprintf(stderr, "IP :: " fmt "\n", ## __VA_ARGS__); } while (0);
#else
#define IP_DEBUG_LOG(...) do { } while (0);
#endif
static uint16_t calculate_checksum_ip(const void *src, size_t len)
{
assert((len & 1) == 0);
const uint16_t *src2 = src;
uint32_t sum = 0xffff;
for (size_t i = 0; i < len/2; i++) {
sum += ntohs(src2[i]);
if (sum > 0xffff)
sum -= 0xffff;
}
return htons(~sum);
}
static ip_plugged_protocol_t *
find_protocol_with_id(ip_state_t *ip_state, uint8_t protocol)
{
for (size_t i = 0; i < ip_state->plugged_protocols_count; i++)
if (protocol == ip_state->plugged_protocols[i].protocol)
return ip_state->plugged_protocols + i;
return NULL;
}
bool ip_plug_protocol(ip_state_t *ip_state, uint8_t protocol,
void *data, void (*process_packet)(void *data, ip_address_t sender, const void *packet, size_t len))
{
if (protocol == IP_PROTOCOL_ICMP)
return false; // Can't override default ICMP module
ip_plugged_protocol_t *p = find_protocol_with_id(ip_state, protocol);
if (!p) {
if (ip_state->plugged_protocols_count == IP_PLUGGED_PROTOCOLS_MAX)
return false;
p = ip_state->plugged_protocols + ip_state->plugged_protocols_count++;
}
p->protocol = protocol;
p->data = data;
p->process_packet = process_packet;
return true;
}
static bool is_packet_one_of_more_fragments(const ip_packet_t *packet)
{
size_t offset = ntohs(packet->fragment_offset) & 0x1FFF;
bool more_fragments = ntohs(packet->fragment_offset) & 0x2000;
return more_fragments || offset;
}
static void send_icmp_packet(void *data, ip_address_t ip, size_t len)
{
ip_state_t *ip_state = data;
// The data was written in the output buffer
ip_packet_t *packet = ip_state->output_ptr; // This changes every iteration
packet->version = 4;
packet->header_length = 5;
packet->type_of_service = 0; // ???
packet->total_length = htons(sizeof(ip_packet_t) + len);
packet->id = ip_state->next_id++;
packet->fragment_offset = 0; // ???
packet->time_to_live = 32; // ???
packet->protocol = IP_PROTOCOL_ICMP;
packet->checksum = 0; // Temporary value
packet->src_ip = ip_state->ip;
packet->dst_ip = ip;
packet->checksum = calculate_checksum_ip((uint16_t*) packet, 4 * packet->header_length);
ip_state->send(ip_state->send_data, ip, sizeof(ip_packet_t) + len);
}
void ip_init(ip_state_t *state,
ip_address_t ip,
void *send_data,
void (*send)(void*, ip_address_t, size_t))
{
state->ip = ip;
state->next_id = 0;
state->send_data = send_data;
state->send = send;
state->output_ptr = NULL;
state->output_max = 0;
state->plugged_protocols_count = 0;
icmp_init(&state->icmp_state, state, send_icmp_packet);
}
void ip_free(ip_state_t *ip_state)
{
icmp_free(&ip_state->icmp_state);
}
void ip_change_output_buffer(ip_state_t *state, void *ptr, size_t max)
{
state->output_ptr = ptr;
state->output_max = max;
icmp_change_output_buffer(&state->icmp_state, (ip_packet_t*) ptr + 1, max - sizeof(ip_packet_t));
}
void ip_seconds_passed(ip_state_t *state, size_t seconds)
{
(void) state;
(void) seconds;
}
int ip_send(ip_state_t *state, ip_protocol_t protocol, ip_address_t dst, bool no_fragm, const void *src, size_t len)
{
size_t managed_payload = 0;
while (managed_payload < len && (managed_payload == 0 || !no_fragm)) {
if (state->output_ptr == NULL) {
// Lower layers of the network stack didn't specify an output
// buffer region. This may be because no memory is available.
// If at least one byte was sent, return gracefully.
// If no byte was sent return an error to the caller.
if (managed_payload > 0)
break;
else
return -1;
}
if (state->output_max <= sizeof(ip_packet_t))
// Output buffer provided by the lower layers of the stack
// isn't big enough for an IP packet containing a single byte.
return -1;
size_t current_payload_limit = state->output_max - sizeof(ip_packet_t);
size_t remaining_payload = len - managed_payload;
size_t considered_payload = MIN(current_payload_limit, remaining_payload);
ip_packet_t *packet = state->output_ptr; // This changes every iteration
packet->version = 4;
packet->header_length = 5;
packet->type_of_service = 0; // ???
packet->total_length = htons(sizeof(ip_packet_t) + considered_payload);
packet->id = state->next_id++;
packet->fragment_offset = 0; // ???
packet->time_to_live = 32; // ???
packet->protocol = protocol;
packet->checksum = 0; // Temporary value
packet->src_ip = state->ip;
packet->dst_ip = dst;
memcpy(packet->payload,
src + managed_payload,
considered_payload);
packet->checksum = calculate_checksum_ip((uint16_t*) packet, 4 * packet->header_length);
// Sending updates the [state->output_ptr] and [state->output_len]
state->send(state->send_data, dst, sizeof(ip_packet_t) + considered_payload);
managed_payload += considered_payload;
}
return managed_payload;
}
void ip_process_packet(ip_state_t *ip_state, const void *packet, size_t len)
{
if (len < sizeof(ip_packet_t))
return;
const ip_packet_t *packet2 = packet;
if (packet2->version != 4 || packet2->header_length < 5) {
IP_DEBUG_LOG("Only supported IPv4 (received %d) with no options", packet2->version);
return;
}
size_t option_count = packet2->header_length - sizeof(ip_packet_t)/4;
if (option_count > 0) {
#warning "TODO: Handle IP options"
return;
}
if (is_packet_one_of_more_fragments(packet2)) {
IP_DEBUG_LOG("Not supporting IP fragmentation");
return;
}
if (calculate_checksum_ip((uint16_t*) packet2, 4 * packet2->header_length)) {
IP_DEBUG_LOG("Dropping IP packet with invalid checksum");
return;
}
if (packet2->dst_ip != ip_state->ip) {
IP_DEBUG_LOG("Packet not for me");
return;
}
ip_plugged_protocol_t *handler = find_protocol_with_id(ip_state, packet2->protocol);
const void *packet3_ptr = packet2+1;
size_t packet3_len = ntohs(packet2->total_length) - sizeof(ip_packet_t);
if (handler)
handler->process_packet(handler->data, packet2->src_ip, packet3_ptr, packet3_len);
else if (packet2->protocol == IP_PROTOCOL_ICMP)
icmp_process_packet(&ip_state->icmp_state, packet2->src_ip, packet3_ptr, packet3_len);
else
IP_DEBUG_LOG("Unsupported protocol %d", packet2->protocol);
}
+67
View File
@@ -0,0 +1,67 @@
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <endian.h>
#include "defs.h"
#include "icmp.h"
#define IP_PLUGGED_PROTOCOLS_MAX 4
typedef struct {
#if __BYTE_ORDER == __LITTLE_ENDIAN
uint8_t header_length: 4;
uint8_t version: 4;
#endif
#if __BYTE_ORDER == __BIG_ENDIAN
uint8_t version: 4;
uint8_t header_length: 4;
#endif
uint8_t type_of_service;
uint16_t total_length;
uint16_t id;
uint16_t fragment_offset;
uint8_t time_to_live;
uint8_t protocol;
uint16_t checksum;
uint32_t src_ip;
uint32_t dst_ip;
char payload[];
} ip_packet_t;
static_assert(sizeof(ip_packet_t) == 20);
typedef enum {
IP_PROTOCOL_ICMP = 1,
IP_PROTOCOL_TCP = 6,
IP_PROTOCOL_UDP = 17,
} ip_protocol_t;
typedef struct {
uint8_t protocol;
void *data;
void (*process_packet)(void*, ip_address_t, const void*, size_t);
} ip_plugged_protocol_t;
typedef struct {
ip_address_t ip;
uint32_t next_id;
void *output_ptr;
size_t output_max;
icmp_state_t icmp_state;
void *send_data;
void (*send)(void*, ip_address_t, size_t);
size_t plugged_protocols_count;
ip_plugged_protocol_t plugged_protocols[IP_PLUGGED_PROTOCOLS_MAX];
} ip_state_t;
void ip_seconds_passed(ip_state_t *state, size_t seconds);
void ip_change_output_buffer(ip_state_t *state, void *ptr, size_t max);
void ip_init(ip_state_t *state, ip_address_t ip, void *send_data, void (*send)(void*, ip_address_t, size_t));
void ip_free(ip_state_t *state);
int ip_send(ip_state_t *state, ip_protocol_t protocol, ip_address_t dst, bool no_fragm, const void *src, size_t len);
void ip_process_packet(ip_state_t *state, const void *packet, size_t len);
bool ip_plug_protocol(ip_state_t *ip_state, uint8_t protocol, void *data, void (*process_packet)(void *data, ip_address_t sender, const void *packet, size_t len));
+758
View File
@@ -0,0 +1,758 @@
#include <time.h> // time()
#include <errno.h>
#include <string.h> // strerror()
#include <stdint.h>
#include <stdlib.h>
#include <netinet/in.h>
#include "ip.h"
#include "arp.h"
#include "tcp.h"
#include <microtcp.h>
#ifdef MICROTCP_BACKGROUND_THREAD
#include <pthread.h>
#endif
#ifdef MICROTCP_DEBUG
#include <stdio.h>
#define MICROTCP_DEBUG_LOG(fmt, ...) do { fprintf(stderr, "MICROTCP :: " fmt "\n", ## __VA_ARGS__); } while (0);
#else
#define MICROTCP_DEBUG_LOG(...) do {} while (0);
#endif
#ifdef MICROTCP_BACKGROUND_THREAD
#define LOCK_WHEN_THREADED(mtcp) do { \
fprintf(stderr, "--- %s before lock\n", __func__); \
fflush(stderr); \
pthread_mutex_lock(&(mtcp)->lock); \
fprintf(stderr, "--- %s after lock\n", __func__); \
fflush(stderr); \
} while (0);
#define UNLOCK_WHEN_THREADED(mtcp) do { \
fprintf(stderr, "--- %s before unlock\n", __func__); \
fflush(stderr); \
pthread_mutex_unlock(&(mtcp)->lock); \
fprintf(stderr, "--- %s after unlock\n", __func__); \
fflush(stderr); \
} while (0);
//#define UNLOCK_WHEN_THREADED(mtcp) do { pthread_mutex_unlock(&(mtcp)->lock); } while (0);
#else
#define LOCK_WHEN_THREADED(mtcp) do { (void) (mtcp); } while (0);
#define UNLOCK_WHEN_THREADED(mtcp) do { (void) (mtcp); } while (0);
#endif
typedef struct buffer_t buffer_t;
struct buffer_t {
microtcp_t *mtcp;
buffer_t *prev;
buffer_t *next;
size_t used;
char data[1518];
};
typedef enum {
SOCKET_LISTENER,
SOCKET_CONNECTION,
} socket_type_t;
struct microtcp_socket_t {
microtcp_t *mtcp;
microtcp_socket_t *prev;
microtcp_socket_t *next;
socket_type_t type;
union {
tcp_listener_t *listener;
tcp_connection_t *connection;
};
#ifdef MICROTCP_BACKGROUND_THREAD
pthread_cond_t something_to_accept;
#endif
};
struct microtcp_t {
time_t last_update_time;
#ifdef MICROTCP_BACKGROUND_THREAD
bool thread_should_stop;
pthread_t thread_id;
pthread_mutex_t lock;
#endif
microtcp_callbacks_t callbacks;
ip_address_t ip;
mac_address_t mac;
ip_state_t ip_state;
arp_state_t arp_state;
tcp_state_t tcp_state;
buffer_t *used_buffer;
buffer_t *wait_buffer_list;
buffer_t *free_buffer_list;
buffer_t buffer_pool[MICROTCP_MAX_BUFFERS];
microtcp_socket_t *used_socket_list;
microtcp_socket_t *free_socket_list;
microtcp_socket_t socket_pool[MICROTCP_MAX_SOCKETS];
};
const char *microtcp_strerror(microtcp_errcode_t errcode)
{
switch (errcode) {
case MICROTCP_ERRCODE_NONE: return "No error occurred";
case MICROTCP_ERRCODE_SOCKETLIMIT: return "Can't create a socket because the socket limit per microtcp instance was reached";
case MICROTCP_ERRCODE_TCPERROR: return "An error occurred at the TCP layer";
case MICROTCP_ERRCODE_BADCONDVAR: return "Condition variable error";
case MICROTCP_ERRCODE_NOTLISTENER: return "Invalid operation on a non-listener socket";
case MICROTCP_ERRCODE_CANTBLOCK: return "Can't execute a blocking call for this function";
case MICROTCP_ERRCODE_NOTHINGTOACCEPT: return "Accept queue is empty";
case MICROTCP_ERRCODE_NOTCONNECTION: return "Invalid operation on a non-connection socket";
}
return "???";
}
typedef enum {
ETHERNET_PROTOCOL_ARP = 0x0806,
ETHERNET_PROTOCOL_IP = 0x0800,
} ethernet_protocol_t;
typedef struct {
mac_address_t dst;
mac_address_t src;
uint16_t proto;
} __attribute__((packed)) ethernet_frame_t;
static_assert(sizeof(ethernet_frame_t) == 14);
#ifdef MICROTCP_DEBUG
static bool is_valid_buffer_pointer(microtcp_t *mtcp, buffer_t *buffer)
{
for (size_t i = 0; i < MICROTCP_MAX_BUFFERS; i++)
if (buffer == mtcp->buffer_pool + i)
return true;
return false;
}
#endif
static void send_arp_packet(void *data, mac_address_t dst)
{
microtcp_t *mtcp = data;
buffer_t *buffer = mtcp->used_buffer;
#ifdef MICROTCP_DEBUG
assert(is_valid_buffer_pointer(mtcp, buffer));
#endif
buffer->used = sizeof(ethernet_frame_t) + sizeof(arp_packet_t);
ethernet_frame_t *frame = (ethernet_frame_t*) buffer->data;
frame->dst = dst;
frame->src = mtcp->mac;
frame->proto = htons(ETHERNET_PROTOCOL_ARP);
// TODO: What about the CRC?
#warning "TODO: Calculate Ethernet CRC"
int n = mtcp->callbacks.send(mtcp->callbacks.data, buffer->data, buffer->used);
if (n < 0)
MICROTCP_DEBUG_LOG("Couldn't send (%s)", strerror(errno));
// Now reset the used buffer
mtcp->used_buffer->used = 0;
}
static int send_tcp_segment(void *data, ip_address_t dst,
const void *str, size_t len)
{
microtcp_t *mtcp = data;
return ip_send(&mtcp->ip_state, IP_PROTOCOL_TCP, dst, true, str, len);
}
static void move_wait_buffer_to_free_list(buffer_t *buffer)
{
microtcp_t *mtcp = buffer->mtcp;
#ifdef MICROTCP_DEBUG
assert(is_valid_buffer_pointer(mtcp, buffer));
assert(buffer->prev == NULL || is_valid_buffer_pointer(mtcp, buffer->prev));
assert(buffer->next == NULL || is_valid_buffer_pointer(mtcp, buffer->next));
#endif
if (buffer->prev)
buffer->prev->next = buffer->next;
else
mtcp->wait_buffer_list = buffer->next;
if (buffer->next)
buffer->next->prev = buffer->prev;
#ifdef MICROTCP_DEBUG
assert(mtcp->free_buffer_list == NULL || is_valid_buffer_pointer(mtcp, mtcp->free_buffer_list));
assert(mtcp->free_buffer_list == NULL || mtcp->free_buffer_list->prev == NULL);
assert(mtcp->free_buffer_list == NULL || mtcp->free_buffer_list->next == NULL || is_valid_buffer_pointer(mtcp, mtcp->free_buffer_list->next));
#endif
buffer->prev = NULL;
buffer->next = mtcp->free_buffer_list;
mtcp->free_buffer_list = buffer;
}
static void mac_resolved(void *data, arp_resolution_status_t status, mac_address_t mac)
{
buffer_t *buffer = data;
microtcp_t *mtcp = buffer->mtcp;
#ifdef MICROTCP_DEBUG
assert(is_valid_buffer_pointer(mtcp, buffer));
#endif
switch (status) {
case ARP_RESOLUTION_OK:
{
ethernet_frame_t *frame = (ethernet_frame_t*) buffer->data;
frame->dst = mac;
int n = mtcp->callbacks.send(mtcp->callbacks.data, buffer->data, buffer->used);
if (n < 0)
MICROTCP_DEBUG_LOG("Couldn't send (%s)", strerror(errno));
}
break;
case ARP_RESOLUTION_FAILED:
MICROTCP_DEBUG_LOG("MAC resolution failed");
break;
case ARP_RESOLUTION_TIMEOUT:
MICROTCP_DEBUG_LOG("MAC resolution timeout");
break;
}
move_wait_buffer_to_free_list(buffer);
}
static void move_used_buffer_to_wait_list(microtcp_t *mtcp)
{
buffer_t *buffer = mtcp->used_buffer;
mtcp->used_buffer = NULL;
#ifdef MICROTCP_DEBUG
assert(is_valid_buffer_pointer(mtcp, buffer));
#endif
buffer->next = mtcp->wait_buffer_list;
if (mtcp->wait_buffer_list)
mtcp->wait_buffer_list->prev = buffer;
mtcp->wait_buffer_list = buffer;
ip_change_output_buffer(&mtcp->ip_state, NULL, 0);
arp_change_output_buffer(&mtcp->arp_state, NULL, 0);
}
static void use_a_buffer(microtcp_t *mtcp)
{
#ifdef MIRCOTCP_DEBUG
assert(mtcp->free_buffer_list == NULL || is_valid_buffer_pointer(mtcp, mtcp->free_buffer_list));
assert(mtcp->free_buffer_list == NULL || mtcp->free_buffer_list->prev == NULL);
assert(mtcp->free_buffer_list == NULL || mtcp->free_buffer_list->next == NULL || is_valid_buffer_pointer(mtcp, mtcp->free_buffer_list->next));
#endif
// At this moment the network stack has no allocated
// output buffer but wants to allocate one (by calling
// this function).
// It's assumed there is no output buffer, hence:
//
assert(mtcp->used_buffer == NULL);
//
// To allocate a buffer, we need to pop it from the
// buffer free list, which is a singly-linked list of
// unused buffers. Once it's been popped off the list,
// we need to tell the upper layers of the stack that
// this is the new output buffer.
//
// If the free list is empty, no buffer is allocated.
//
if (!mtcp->free_buffer_list)
return; // No free buffers available in the free list.
//
// Pop a buffer from the free list
buffer_t *buffer = mtcp->free_buffer_list;
mtcp->free_buffer_list = buffer->next;
//
// Initialize the buffer
buffer->mtcp = mtcp;
buffer->used = 0;
buffer->prev = NULL;
buffer->next = NULL;
//
// Set it as the output buffer
mtcp->used_buffer = buffer;
//
// Now tell the upper layers where they'll output
// the data, but reserve the first bytes of the buffer
// for the ethernet header.
//
void *output_ptr = buffer->data + sizeof(ethernet_frame_t);
size_t output_max = sizeof(buffer->data) - sizeof(ethernet_frame_t);
ip_change_output_buffer(&mtcp->ip_state, output_ptr, output_max);
arp_change_output_buffer(&mtcp->arp_state, output_ptr, output_max);
}
static void send_ip_packet(void *data, ip_address_t ip, size_t len)
{
microtcp_t *mtcp = data;
buffer_t *buffer = mtcp->used_buffer;
if (buffer == NULL)
// The IP layer wants to send something, but no output
// buffer was associated to it. This function should not
// have been called by the IP layer without a buffer.
return;
buffer->used = sizeof(ethernet_frame_t) + len;
move_used_buffer_to_wait_list(mtcp);
use_a_buffer(mtcp);
ethernet_frame_t *frame = (ethernet_frame_t*) buffer->data;
frame->src = mtcp->mac;
frame->dst = MAC_ZERO; // We need to determine it
frame->proto = htons(ETHERNET_PROTOCOL_IP);
arp_resolve_mac(&mtcp->arp_state, ip, buffer, mac_resolved);
}
static void
tcp_process_segment_wrapper(void *data, ip_address_t ip, const void *packet, size_t len)
{
if (len >= sizeof(tcp_segment_t))
tcp_process_segment((tcp_state_t*) data, ip, (tcp_segment_t*) packet, len);
}
static void
process_packet(microtcp_t *mtcp, const void *packet, size_t len)
{
if (len < sizeof(ethernet_frame_t))
return;
const ethernet_frame_t *frame = packet;
switch (ntohs(frame->proto)) {
case ETHERNET_PROTOCOL_ARP:
arp_process_packet(&mtcp->arp_state, frame+1, len - sizeof(ethernet_frame_t));
break;
case ETHERNET_PROTOCOL_IP:
ip_process_packet(&mtcp->ip_state, frame+1, len - sizeof(ethernet_frame_t));
break;
default:
// Unsupported ethertype
MICROTCP_DEBUG_LOG("Ignoring packet with ethertype %4x", frame->proto);
break;
}
}
void microtcp_process_packet(microtcp_t *mtcp, const void *packet, size_t len)
{
LOCK_WHEN_THREADED(mtcp);
process_packet(mtcp, packet, len);
UNLOCK_WHEN_THREADED(mtcp);
}
void microtcp_step(microtcp_t *mtcp)
{
char packet[UINT16_MAX];
// The call to [recv] (which is assumed to be blocking)
// needs to be out of the critical section to give other
// threads the ability to progress in the mean time.
int size = mtcp->callbacks.recv(mtcp->callbacks.data, packet, sizeof(packet));
if (size < 0)
return;
LOCK_WHEN_THREADED(mtcp);
{
process_packet(mtcp, packet, size);
time_t current_time = time(NULL);
int secs = (float) (current_time - mtcp->last_update_time);
if (secs > 0) {
ip_seconds_passed(&mtcp->ip_state, secs);
arp_seconds_passed(&mtcp->arp_state, secs);
tcp_seconds_passed(&mtcp->tcp_state, secs);
mtcp->last_update_time = current_time;
}
}
unlock_and_exit:
UNLOCK_WHEN_THREADED(mtcp);
}
#ifdef MICROTCP_BACKGROUND_THREAD
static void *loop(void *data)
{
microtcp_t *mtcp = data;
while (!mtcp->thread_should_stop)
microtcp_step(mtcp);
return NULL;
}
#endif
microtcp_t *microtcp_create_using_callbacks(microtcp_ip_t ip, microtcp_mac_t mac,
microtcp_callbacks_t callbacks)
{
microtcp_t *mtcp = malloc(sizeof(microtcp_t));
if (mtcp == NULL)
return NULL;
mac_address_t mac2;
static_assert(sizeof(mac2) == sizeof(mac));
memcpy(&mac2, &mac, sizeof(mac));
mtcp->ip = ip;
mtcp->mac = mac2;
mtcp->callbacks = callbacks;
mtcp->last_update_time = time(NULL);
mtcp->used_buffer = NULL;
mtcp->wait_buffer_list = NULL;
mtcp->free_buffer_list = mtcp->buffer_pool;
for (size_t i = 0; i < MICROTCP_MAX_BUFFERS-1; i++) {
mtcp->buffer_pool[i].mtcp = NULL;
mtcp->buffer_pool[i].prev = NULL;
mtcp->buffer_pool[i].next = mtcp->buffer_pool + i+1;
}
mtcp->buffer_pool[MICROTCP_MAX_BUFFERS-1].mtcp = NULL;
mtcp->buffer_pool[MICROTCP_MAX_BUFFERS-1].prev = NULL;
mtcp->buffer_pool[MICROTCP_MAX_BUFFERS-1].next = NULL;
mtcp->used_socket_list = NULL;
mtcp->free_socket_list = mtcp->socket_pool;
for (size_t i = 0; i < MICROTCP_MAX_SOCKETS-1; i++) {
mtcp->socket_pool[i].mtcp = NULL;
mtcp->socket_pool[i].prev = NULL;
mtcp->socket_pool[i].next = mtcp->socket_pool + i + 1;
}
mtcp->socket_pool[MICROTCP_MAX_SOCKETS-1].mtcp = NULL;
mtcp->socket_pool[MICROTCP_MAX_SOCKETS-1].prev = NULL;
mtcp->socket_pool[MICROTCP_MAX_SOCKETS-1].next = NULL;
ip_init(&mtcp->ip_state, ip, mtcp, send_ip_packet);
if (!ip_plug_protocol(&mtcp->ip_state, IP_PROTOCOL_TCP, &mtcp->tcp_state, tcp_process_segment_wrapper)) {
free(mtcp);
return NULL;
}
arp_init(&mtcp->arp_state, ip, mac2, mtcp, send_arp_packet);
tcp_init(&mtcp->tcp_state, (tcp_callbacks_t) {
.data = mtcp,
.send = send_tcp_segment,
});
use_a_buffer(mtcp);
#ifdef MICROTCP_BACKGROUND_THREAD
{
if (pthread_mutex_init(&mtcp->lock, NULL)) {
ip_free(&mtcp->ip_state);
arp_free(&mtcp->arp_state);
tcp_free(&mtcp->tcp_state);
free(mtcp);
return NULL;
}
mtcp->thread_should_stop = false;
if (pthread_create(&mtcp->thread_id, NULL, loop, mtcp)) {
ip_free(&mtcp->ip_state);
arp_free(&mtcp->arp_state);
tcp_free(&mtcp->tcp_state);
free(mtcp);
return NULL;
}
}
#endif
MICROTCP_DEBUG_LOG("Instanciated ("
"debug="
#ifdef MICROTCP_DEBUG
"yes"
#else
"no"
#endif
", thread="
#ifdef MICROTCP_BACKGROUND_THREAD
"yes"
#else
"no"
#endif
")");
return mtcp;
}
void microtcp_destroy(microtcp_t *mtcp)
{
#ifdef MICROTCP_BACKGROUND_THREAD
MICROTCP_DEBUG_LOG("Stopping thread");
mtcp->thread_should_stop = true;
pthread_join(mtcp->thread_id, NULL);
pthread_mutex_destroy(&mtcp->lock);
MICROTCP_DEBUG_LOG("Thread stopped");
#endif
ip_free(&mtcp->ip_state);
arp_free(&mtcp->arp_state);
tcp_free(&mtcp->tcp_state);
if (mtcp->callbacks.free)
mtcp->callbacks.free(mtcp->callbacks.data);
}
static microtcp_socket_t*
pop_socket_struct_from_free_list(microtcp_t *mtcp)
{
microtcp_socket_t *socket = mtcp->free_socket_list;
mtcp->free_socket_list = socket->next;
return socket;
}
static bool
have_unused_socket_structs(microtcp_t *mtcp)
{
return mtcp->free_socket_list != NULL;
}
static void
push_unlinked_socket_into_used_list(microtcp_socket_t *socket)
{
microtcp_t *mtcp = socket->mtcp;
socket->next = mtcp->used_socket_list;
if (mtcp->used_socket_list)
mtcp->used_socket_list->prev = socket;
mtcp->used_socket_list = socket;
}
static void
unlink_socket_from_used_socket_list(microtcp_socket_t *socket)
{
microtcp_t *mtcp = socket->mtcp;
if (socket->prev)
socket->prev->next = socket->next;
else
mtcp->used_socket_list = socket->next;
if (socket->next)
socket->next->prev = socket->prev;
socket->prev = NULL;
socket->next = NULL;
}
static void
push_unlinked_socket_into_free_list(microtcp_t *mtcp, microtcp_socket_t *socket)
{
socket->prev = NULL;
socket->next = mtcp->free_socket_list;
mtcp->free_socket_list = socket;
}
static void ready_to_accept(void *data)
{
#ifdef MICROTCP_BACKGROUND_THREAD
microtcp_socket_t *socket = data;
pthread_cond_signal(&socket->something_to_accept);
#else
(void) data;
#endif
}
microtcp_socket_t *microtcp_open(microtcp_t *mtcp, uint16_t port,
microtcp_errcode_t *errcode)
{
microtcp_errcode_t errcode2 = MICROTCP_ERRCODE_NONE;
microtcp_socket_t *socket = NULL;
LOCK_WHEN_THREADED(mtcp);
{
socket = pop_socket_struct_from_free_list(mtcp);
if (!socket) {
errcode2 = MICROTCP_ERRCODE_SOCKETLIMIT;
goto unlock_and_exit; // Socket limit reached
}
tcp_listener_t *listener = tcp_listener_create(&mtcp->tcp_state, port, socket, ready_to_accept);
if (listener == NULL) {
#warning "This error code should be more specific, but the TCP module isn't stable yet"
errcode2 = MICROTCP_ERRCODE_TCPERROR;
push_unlinked_socket_into_free_list(mtcp, socket);
goto unlock_and_exit;
}
socket->mtcp = mtcp;
socket->prev = NULL;
socket->next = NULL;
socket->type = SOCKET_LISTENER;
socket->listener = listener;
#ifdef MICROTCP_BACKGROUND_THREAD
if (pthread_cond_init(&socket->something_to_accept, NULL)) {
errcode2 = MICROTCP_ERRCODE_BADCONDVAR;
push_unlinked_socket_into_free_list(mtcp, socket);
tcp_listener_destroy(listener);
goto unlock_and_exit;
}
#endif
push_unlinked_socket_into_used_list(socket);
}
unlock_and_exit:
UNLOCK_WHEN_THREADED(mtcp);
if (errcode)
*errcode = errcode2;
return socket;
}
void microtcp_close(microtcp_socket_t *socket)
{
if (!socket)
return;
microtcp_t *mtcp = socket->mtcp;
LOCK_WHEN_THREADED(mtcp);
{
switch (socket->type) {
case SOCKET_LISTENER:
#ifdef MICROTCP_BACKGROUND_THREAD
pthread_cond_destroy(&socket->something_to_accept);
#endif
tcp_listener_destroy(socket->listener);
break;
case SOCKET_CONNECTION:
tcp_connection_destroy(socket->connection);
break;
}
unlink_socket_from_used_socket_list(socket);
push_unlinked_socket_into_free_list(mtcp, socket);
}
UNLOCK_WHEN_THREADED(mtcp);
}
microtcp_socket_t *microtcp_accept(microtcp_socket_t *socket,
bool no_block,
microtcp_errcode_t *errcode)
{
microtcp_errcode_t errcode2 = MICROTCP_ERRCODE_NONE;
microtcp_t *mtcp = socket->mtcp;
microtcp_socket_t *socket2 = NULL;
LOCK_WHEN_THREADED(mtcp);
{
if (socket->type != SOCKET_LISTENER) {
errcode2 = MICROTCP_ERRCODE_NOTLISTENER;
goto unlock_and_exit; // Can't accept from a non-listening socket
}
if (!have_unused_socket_structs(mtcp)) {
errcode2 = MICROTCP_ERRCODE_SOCKETLIMIT;
goto unlock_and_exit; // Socket limit reached
}
tcp_connection_t *connection = tcp_listener_accept(socket->listener);
if (!connection) {
#ifdef MICROTCP_BACKGROUND_THREAD
if (no_block) {
errcode2 = MICROTCP_ERRCODE_NOTHINGTOACCEPT;
goto unlock_and_exit;
}
do {
pthread_cond_wait(&socket->something_to_accept, &mtcp->lock);
connection = tcp_listener_accept(socket->listener);
} while (!connection);
#else
if (!no_block)
errcode2 = MICROTCP_ERRCODE_CANTBLOCK;
else
errcode2 = MICROTCP_ERRCODE_NOTHINGTOACCEPT;
goto unlock_and_exit;
#endif
}
socket2 = pop_socket_struct_from_free_list(mtcp);
assert(socket2); // Because we checked at the start
socket2->mtcp = mtcp;
socket2->prev = NULL;
socket2->next = NULL;
socket2->type = SOCKET_CONNECTION;
socket2->connection = connection;
push_unlinked_socket_into_used_list(socket2);
}
unlock_and_exit:
UNLOCK_WHEN_THREADED(mtcp);
if (errcode)
*errcode = errcode2;
return socket2;
}
size_t microtcp_recv(microtcp_socket_t *socket,
void *dst, size_t len,
microtcp_errcode_t *errcode)
{
if (!socket || socket->type != SOCKET_CONNECTION) {
if (errcode)
*errcode = MICROTCP_ERRCODE_NOTCONNECTION;
return 0;
}
microtcp_t *mtcp = socket->mtcp;
LOCK_WHEN_THREADED(mtcp);
size_t num = tcp_connection_recv(socket->connection, dst, len);
UNLOCK_WHEN_THREADED(mtcp);
if (errcode)
*errcode = MICROTCP_ERRCODE_NONE;
return num;
}
size_t microtcp_send(microtcp_socket_t *socket,
const void *src, size_t len,
microtcp_errcode_t *errcode)
{
if (!socket || socket->type != SOCKET_CONNECTION) {
if (errcode)
*errcode = MICROTCP_ERRCODE_NOTCONNECTION;
return 0;
}
microtcp_t *mtcp = socket->mtcp;
LOCK_WHEN_THREADED(mtcp);
size_t num = tcp_connection_send(socket->connection, src, len);
UNLOCK_WHEN_THREADED(mtcp);
if (errcode)
*errcode = MICROTCP_ERRCODE_NONE;
return num;
}
+181
View File
@@ -0,0 +1,181 @@
#ifdef MICROTCP_LINUX
#include <poll.h>
#include <errno.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <net/if_arp.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "defs.h"
#include "microtcp.h"
static int systemf(const char *fmt, ...)
{
char buffer[256];
va_list va;
va_start(va, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, va);
va_end(va);
fprintf(stderr, "INFO: Running [%s]\n", buffer);
return system(buffer);
}
static int create_tun_device(const char *dev, char dev2[IFNAMSIZ])
{
int fd = open("/dev/net/tun", O_RDWR);
if (fd < 0)
return -1;
struct ifreq ifr;
memset(&ifr, 0, sizeof(struct ifreq));
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
if (dev && *dev)
strncpy(ifr.ifr_name, dev, IFNAMSIZ);
int err = ioctl(fd, TUNSETIFF, &ifr);
if (err) {
close(fd);
return -1;
}
strncpy(dev2, ifr.ifr_name, IFNAMSIZ);
return fd;
}
static int send_callback(void *data, const void *src, size_t len)
{
int tap_fd = (int) data;
return write(tap_fd, src, len);
}
static int recv_callback(void *data, void *dst, size_t len)
{
int tap_fd = (int) data;
int timeout = 1000;
struct pollfd pfd = {.fd=tap_fd, .events=POLLIN};
int status = poll(&pfd, 1, timeout);
if (status < 1) {
return status;
}
int num = read(tap_fd, dst, len);
return num;
}
static void free_callback(void *data)
{
int tap_fd = (int) data;
close(tap_fd);
}
/*
static bool get_ip_address(const char *dev, microtcp_ip_t *ip)
{
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0)
return false;
struct ifreq ifr;
memset(&ifr, 0, sizeof(struct ifreq));
ifr.ifr_addr.sa_family = AF_INET;
strncpy(ifr.ifr_name, dev, IFNAMSIZ-1);
ioctl(fd, SIOCGIFADDR, &ifr);
memcpy(ip, &((struct sockaddr_in*) &ifr.ifr_addr)->sin_addr, sizeof(microtcp_ip_t));
close(fd);
return true;
}
*/
static bool get_mac_address(const char *dev, microtcp_mac_t *mac)
{
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0)
return false;
struct ifreq ifr;
memset(&ifr, 0, sizeof(struct ifreq));
strncpy(ifr.ifr_name, dev, IFNAMSIZ-1);
ioctl(fd, SIOCGIFHWADDR, &ifr);
if (ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER)
return false;
memcpy(mac, ifr.ifr_hwaddr.sa_data, sizeof(microtcp_mac_t));
close(fd);
return true;
}
microtcp_t *microtcp_create()
{
char dev[IFNAMSIZ];
int tap_fd = create_tun_device("tap0", dev);
if (tap_fd < 0) {
fprintf(stderr, "ERROR: Failed creating TAP device (%s)\n", strerror(errno));
return NULL;
}
fprintf(stderr, "INFO: Using TAP device %s\n", dev);
const char *tap_addr = "10.0.0.5";
const char *tap_route = "10.0.0.0/24";
systemf("ip link set dev %s up", dev);
systemf("ip route add dev %s %s", dev, tap_route);
systemf("ip address add dev %s local %s", dev, tap_addr);
systemf("sudo sysctl -w net.ipv4.ip_forward=1");
microtcp_mac_t mac;
if (!get_mac_address(dev, &mac)) {
fprintf(stderr, "FATAL: Failed to query NIC for IP or MAC (%s)\n", strerror(errno));
close(tap_fd);
return NULL;
}
mac.data[5]++;
microtcp_ip_t ip;
inet_pton(AF_INET, "10.0.0.4", &ip);
struct in_addr ip2;
ip2.s_addr = ip;
fprintf(stderr, "INFO: Using IP %s\n", inet_ntoa(ip2));
fprintf(stderr, "INFO: Using MAC %x:%x:%x:%x:%x:%x\n", mac.data[0], mac.data[1], mac.data[2], mac.data[3], mac.data[4], mac.data[5]);
microtcp_callbacks_t callbacks = {
.data = (int) tap_fd,
.send = send_callback,
.recv = recv_callback,
.free = free_callback,
};
microtcp_t *mtcp = microtcp_create_using_callbacks(ip, mac, callbacks);
if (!mtcp) {
close(tap_fd);
return NULL;
}
return mtcp;
}
#endif /* MICROTCP_LINUX */
+468
View File
@@ -0,0 +1,468 @@
#include <string.h>
#include <arpa/inet.h>
#include "tcp.h"
#ifdef TCP_DEBUG
#include <stdio.h>
#define TCP_DEBUG_LOG(fmt, ...) fprintf(stderr, "TCP :: " fmt "\n", ## __VA_ARGS__)
#else
#define TCP_DEBUG_LOG(...)
#endif
static int tcp_send(tcp_state_t *tcp_state, ip_address_t ip,
const void *src, size_t len)
{
return tcp_state->callbacks.send(tcp_state->callbacks.data, ip, src, len);
}
void tcp_init(tcp_state_t *tcp_state, tcp_callbacks_t callbacks)
{
tcp_state->callbacks = callbacks;
for (size_t i = 0; i < TCP_MAX_SOCKETS-1; i++)
tcp_state->connection_pool[i].next = tcp_state->connection_pool + i+1;
tcp_state->connection_pool[TCP_MAX_SOCKETS-1].next = NULL;
tcp_state->free_connection_list = tcp_state->connection_pool;
tcp_state->used_connection_list = NULL;
for (size_t i = 0; i < TCP_MAX_LISTENERS-1; i++)
tcp_state->listener_pool[i].next = tcp_state->listener_pool + i+1;
tcp_state->listener_pool[TCP_MAX_LISTENERS-1].next = NULL;
tcp_state->free_listener_list = tcp_state->listener_pool;
tcp_state->used_listener_list = NULL;
}
void tcp_free(tcp_state_t *tcp_state)
{
// Destroy all listening connections
while (tcp_state->used_listener_list != NULL)
tcp_listener_destroy(tcp_state->used_listener_list);
}
void tcp_seconds_passed(tcp_state_t *state, size_t seconds)
{
(void) state;
(void) seconds;
}
static tcp_connection_t*
connection_create_waiting_for_ack(tcp_listener_t *listener,
uint32_t seq_no, uint32_t ack_no,
ip_address_t peer_ip, uint16_t peer_port)
{
tcp_state_t *state = listener->state;
// Pop a connection structure from the free list
if (state->free_connection_list == NULL)
// ERROR: Reached connection limit
return NULL;
tcp_connection_t *connection = state->free_connection_list;
state->free_connection_list = connection->next;
// Initialize connection structure
connection->listener = listener;
connection->seq_no = seq_no;
connection->ack_no = ack_no;
connection->peer_port = peer_port;
connection->peer_ip = peer_ip;
connection->in_used = 0;
connection->out_used = 0;
connection->prev = NULL;
connection->next = NULL;
// Append the connection to the list of connections
// waiting for the ACK message
if (listener->connections_waiting_for_ack)
listener->connections_waiting_for_ack->prev = connection;
connection->next = listener->connections_waiting_for_ack;
listener->connections_waiting_for_ack = connection;
return connection;
}
static tcp_listener_t*
find_listener_with_port(tcp_state_t *state, uint16_t port)
{
TCP_DEBUG_LOG("Looking for listener with port %d", port);
tcp_listener_t *cursor = state->used_listener_list;
while (cursor) {
TCP_DEBUG_LOG("port=%d, seeking=%d", cursor->port, port);
if (cursor->port == port)
return cursor;
cursor = cursor->next;
}
return NULL;
}
#include <stdio.h>
typedef enum {
SOCKET_WAIT,
SOCKET_IDLE,
SOCKET_NONE,
} connection_state_t;
static tcp_connection_t *find_connection(tcp_connection_t *list, ip_address_t peer_ip, uint16_t peer_port)
{
tcp_connection_t *cursor = list;
while (cursor) {
if (cursor->peer_ip == peer_ip && cursor->peer_port == peer_port)
return cursor;
cursor = cursor->next;
}
return NULL;
}
static connection_state_t find_connection_associated_to(tcp_listener_t *listener, ip_address_t peer_ip, uint16_t peer_port, tcp_connection_t **connection)
{
tcp_connection_t *connection2 = find_connection(listener->connections, peer_ip, peer_port);
if (connection2) {
*connection = connection2;
return SOCKET_IDLE;
}
connection2 = find_connection(listener->connections_waiting_for_accept_head, peer_ip, peer_port);
if (connection2) {
*connection = connection2;
return SOCKET_IDLE;
}
connection2 = find_connection(listener->connections_waiting_for_ack, peer_ip, peer_port);
if (connection2) {
*connection = connection2;
return SOCKET_WAIT;
}
*connection = NULL;
return SOCKET_NONE;
}
static uint32_t choose_ack()
{
return 0;
}
void tcp_process_segment(tcp_state_t *state, ip_address_t sender,
tcp_segment_t *segment, size_t len)
{
(void) state;
(void) sender;
(void) segment;
(void) len;
TCP_DEBUG_LOG("Received TCP segment");
if (len < sizeof(tcp_segment_t))
return;
uint16_t reordered_dst_port = ntohs(segment->dst_port);
uint16_t reordered_src_port = ntohs(segment->src_port);
tcp_listener_t *listener = find_listener_with_port(state, reordered_dst_port);
if (listener == NULL) {
// No connection is listening on this port. Silently drop the packet.
TCP_DEBUG_LOG("Segment sent to port %d, which is closed", reordered_dst_port);
return;
}
tcp_connection_t *connection;
connection_state_t connection_state = find_connection_associated_to(listener, sender, reordered_src_port, &connection);
if ((segment->flags & TCP_FLAG_SYN) && !(segment->flags & TCP_FLAG_ACK)) {
/* Connection request */
if (connection_state != SOCKET_NONE) {
// Peer wants to connect, but a connection was already created..
// What to do?
#warning "TODO: Handle case where an existing connection recieved the SYN message"
return;
}
// Temporary
uint32_t seq_no = ntohs(segment->seq_no);
uint32_t ack_no = choose_ack();
tcp_connection_t *connection = connection_create_waiting_for_ack(listener, seq_no, ack_no, sender, reordered_dst_port);
if (connection == NULL) {
// ERROR: Socket limit reached. Drop the connection silently
#warning "TODO: Handle connection limit reached (RST?)"
return;
}
tcp_segment_t segment2 = {
.src_port = segment->dst_port,
.dst_port = segment->src_port,
.flags = TCP_FLAG_SYN | TCP_FLAG_ACK, // No need for fixing endianess, it's just one byte!
.seq_no = htons(ack_no),
.ack_no = htons(seq_no),
.offset = 5,
.unused = 0,
.window = htons(TCP_INPUT_BUFFER_SIZE - connection->in_used),
.checksum = 0,
.urgent_pointer = 0,
};
#warning "TODO: Calculare checksum"
tcp_send(state, sender, &segment2, sizeof(segment2));
#warning "TODO: Handle TCP connection creation"
} else if ((segment->flags & TCP_FLAG_SYN) && (segment->flags & TCP_FLAG_ACK)) {
// Drop the packet. We only do servers for now!
#warning "TODO: Handle TCP second message of three way handshake"
} else if (!(segment->flags & TCP_FLAG_SYN) && (segment->flags & TCP_FLAG_ACK)) {
if (connection_state != SOCKET_WAIT) {
// Either there is no connection or the connection wasn't
// waiting for an ACK segment. What to do?
#warning "TODO: Handle case where an existing connection recieved the SYN message"
return;
}
// Move connection from the waiting-for-ack list
// to the waiting-for-accept queue.
// Unlink it from the current list
{
if (connection->prev)
connection->prev->next = connection->next;
else
listener->connections_waiting_for_ack = connection->next;
if (connection->next)
connection->next->prev = connection->prev;
}
// Push it to the new one
{
connection->prev = NULL;
connection->next = listener->connections_waiting_for_accept_head;
if (listener->connections_waiting_for_accept_head)
// Accept queue isn't empty
listener->connections_waiting_for_accept_head->prev = connection;
else
// Accept queue is empty
listener->connections_waiting_for_accept_tail = connection;
listener->connections_waiting_for_accept_head = connection;
}
if (listener->callback)
listener->callback(listener->data);
// TODO: What about the payload?
#warning "TODO: Handle payload of TCP ACK message"
} else {
if (connection_state != SOCKET_IDLE) {
// Either there is no connection associated to this peer
// at this port, or the connection is waiting for an ACK.
// What to do?
#warning "TODO: Handle case where unexpected TCP data message is received"
return;
}
char *payload = segment->payload;
size_t payload_arrived = len - sizeof(tcp_segment_t);
size_t payload_capacity = TCP_INPUT_BUFFER_SIZE - connection->in_used;
size_t payload_considered = MIN(payload_arrived, payload_capacity);
memcpy(connection->in_buffer + connection->in_used, payload, payload_considered);
connection->in_used += payload_considered;
connection->ack_no += payload_considered; // Is this right?
}
}
tcp_listener_t*
tcp_listener_create(tcp_state_t *state, uint16_t port, void *data, void (*callback)(void*))
{
if (find_listener_with_port(state, port)) {
// ERROR: A connection is already listening on this port
TCP_DEBUG_LOG("Faile to create listener on port %d because there already exists one", port);
return NULL;
}
// Pop a listener connection structure from the free list
if (state->free_listener_list == NULL) {
// ERROR: Reached listener connection limit
TCP_DEBUG_LOG("TCP connection limit");
return NULL;
}
tcp_listener_t *listener = state->free_listener_list;
state->free_listener_list = listener->next;
// Initialize listener structure
listener->state = state;
listener->port = port;
listener->connections = NULL;
listener->connections_waiting_for_ack = NULL;
listener->connections_waiting_for_accept_head = NULL;
listener->connections_waiting_for_accept_tail = NULL;
listener->data = data;
listener->callback = callback;
// Push listener connection structure to the used list
listener->prev = NULL;
listener->next = state->used_listener_list;
if (state->used_listener_list)
state->used_listener_list->prev = listener;
state->used_listener_list = listener;
return listener;
}
void tcp_listener_destroy(tcp_listener_t *listener)
{
// TODO: Close all connections
tcp_state_t *state = listener->state;
// Pop listener from used list
{
// Update the reference to the listener of
// the one that precedes it in the list
if (listener->prev)
listener->prev->next = listener->next;
else
state->used_listener_list = listener->next;
// Update the reference to the listener of
// the one that follows it in the list
if (listener->next != NULL)
listener->next->prev = listener->prev;
}
// Push the listener in the free list
listener->next = state->free_listener_list;
state->free_listener_list = listener;
}
tcp_connection_t *tcp_listener_accept(tcp_listener_t *listener)
{
(void) listener;
if (!listener->connections_waiting_for_accept_tail)
return NULL;
tcp_connection_t *connection;
// Pop connection from the accept queue
{
connection = listener->connections_waiting_for_accept_tail;
if (connection->prev)
connection->prev->next = NULL;
else
listener->connections_waiting_for_accept_head = NULL;
listener->connections_waiting_for_accept_tail = connection->next;
}
// Push it to idle list
{
connection->prev = NULL;
connection->next = listener->connections;
if (listener->connections)
listener->connections->prev = connection;
listener->connections = connection;
}
return connection;
}
void tcp_connection_destroy(tcp_connection_t *connection)
{
// NOTE: This can only be called when the
// connection was accepted.
tcp_listener_t *listener = connection->listener;
// Pop connection from the idle connection list
if (connection->prev)
connection->prev->next = connection->next;
else
listener->connections = connection->next;
// Push it into the free connection list
tcp_state_t *state = listener->state;
connection->prev = NULL;
connection->next = state->free_connection_list;
state->free_connection_list = connection;
}
size_t tcp_connection_recv(tcp_connection_t *connection,
void *dst, size_t len)
{
size_t num = MIN(len, connection->in_used);
memcpy(dst, connection->in_buffer, num);
memmove(connection->in_buffer, connection->in_buffer + num, connection->in_used - num);
connection->in_used -= num;
return num;
}
static size_t
append_to_output_buffer(tcp_connection_t *connection,
const void *src, size_t len)
{
size_t num = MIN(len, TCP_OUTPUT_BUFFER_SIZE - connection->out_used);
memcpy(connection->out_buffer + connection->out_used, src, num);
connection->out_used += len;
return num;
}
static uint32_t calculate_checksum(const void *data, size_t size)
{
(void) data,
(void) size;
#warning "TODO: Calculate TCP checksum"
return 0;
}
static void
try_flushing_output_buffer(tcp_connection_t *connection)
{
tcp_state_t *tcp_state = connection->listener->state;
tcp_segment_t *segment = &connection->out_header;
segment->src_port = htons(connection->listener->port);
segment->dst_port = htons(connection->peer_port);
segment->seq_no = htons(connection->seq_no); // Should this be increased by the segment size?
segment->ack_no = htons(connection->ack_no);
segment->unused = 0;
segment->offset = 5; // No options
segment->flags = 0;
segment->window = htons(TCP_INPUT_BUFFER_SIZE - connection->in_used);
segment->checksum = 0; // Temporary value
segment->urgent_pointer = 0; // Don't support urgent data
segment->checksum = calculate_checksum(segment, sizeof(tcp_segment_t));
int sent_bytes = tcp_send(tcp_state, connection->peer_ip, segment, sizeof(tcp_segment_t) + connection->out_used);
if (sent_bytes < 0) {
// It wasn't possible to send out bytes. We'll try again later!
} else {
memmove(connection->out_buffer, connection->out_buffer + sent_bytes, connection->out_used - sent_bytes);
connection->out_used -= sent_bytes;
}
}
size_t tcp_connection_send(tcp_connection_t *connection, const void *src, size_t len)
{
size_t num = append_to_output_buffer(connection, src, len);
try_flushing_output_buffer(connection);
return num;
}
+96
View File
@@ -0,0 +1,96 @@
#include <stddef.h>
#include <stdint.h>
#include <endian.h>
#include "defs.h"
#define TCP_MAX_LISTENERS 32
#define TCP_MAX_SOCKETS 1024
#define TCP_INPUT_BUFFER_SIZE 1024
#define TCP_OUTPUT_BUFFER_SIZE 1024
typedef struct tcp_state_t tcp_state_t; // Predeclare for cyclic references
#define TCP_FLAG_FIN 0x01
#define TCP_FLAG_SYN 0x02
#define TCP_FLAG_RST 0x04
#define TCP_FLAG_PUSH 0x08
#define TCP_FLAG_ACK 0x10
#define TCP_FLAG_URG 0x20
typedef struct {
uint16_t src_port;
uint16_t dst_port;
uint32_t seq_no;
uint32_t ack_no;
#if __BYTE_ORDER == __LITTLE_ENDIAN
uint8_t unused: 4;
uint8_t offset: 4;
#endif
#if __BYTE_ORDER == __BIG_ENDIAN
uint8_t offset: 4;
uint8_t unused: 4;
#endif
uint8_t flags;
uint16_t window;
uint16_t checksum;
uint16_t urgent_pointer;
char payload[];
} tcp_segment_t;
typedef struct tcp_connection_t tcp_connection_t;
typedef struct tcp_listener_t tcp_listener_t;
struct tcp_listener_t {
tcp_state_t *state;
tcp_listener_t *prev;
tcp_listener_t *next;
uint16_t port;
tcp_connection_t *connections;
tcp_connection_t *connections_waiting_for_ack;
tcp_connection_t *connections_waiting_for_accept_head;
tcp_connection_t *connections_waiting_for_accept_tail;
void (*callback)(void*);
void *data;
};
struct tcp_connection_t {
tcp_listener_t *listener; // Listener that accepted this connection
tcp_connection_t *next;
tcp_connection_t *prev;
ip_address_t peer_ip;
uint16_t peer_port;
uint32_t seq_no;
uint32_t ack_no;
size_t in_used;
size_t out_used;
tcp_segment_t out_header; // There must be no padding between
char out_buffer[TCP_OUTPUT_BUFFER_SIZE]; // these two
char in_buffer[TCP_INPUT_BUFFER_SIZE];
};
static_assert(offsetof(tcp_connection_t, out_buffer) == offsetof(tcp_connection_t, out_header) + sizeof(tcp_segment_t));
typedef struct {
void *data;
int (*send)(void *data, ip_address_t ip, const void *src, size_t len);
} tcp_callbacks_t;
struct tcp_state_t {
tcp_callbacks_t callbacks;
tcp_connection_t *used_connection_list;
tcp_connection_t *free_connection_list;
tcp_connection_t connection_pool[TCP_MAX_SOCKETS];
tcp_listener_t *used_listener_list;
tcp_listener_t *free_listener_list;
tcp_listener_t listener_pool[TCP_MAX_LISTENERS];
};
void tcp_init(tcp_state_t *tcp_state, tcp_callbacks_t callbacks);
void tcp_free(tcp_state_t *tcp_state);
void tcp_seconds_passed(tcp_state_t *state, size_t seconds);
void tcp_process_segment(tcp_state_t *state, ip_address_t sender, tcp_segment_t *segment, size_t len);
tcp_listener_t *tcp_listener_create(tcp_state_t *state, uint16_t port, void *data, void (*callback)(void*));
void tcp_listener_destroy(tcp_listener_t *listener);
tcp_connection_t *tcp_listener_accept(tcp_listener_t *listener);
void tcp_connection_destroy(tcp_connection_t *connection);
size_t tcp_connection_recv(tcp_connection_t *connection, void *dst, size_t len);
size_t tcp_connection_send(tcp_connection_t *connection, const void *src, size_t len);