commit 00e68532d148a7b033967ab86563401716939945 Author: Francesco Cozzuto Date: Mon Mar 20 09:43:11 2023 +0100 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9d075da --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.pcapng +loop +test_arp +test_arp_cov +report_arp_cov \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f474157 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +New-NetFirewallRule -DisplayName "WSL" -Direction Inbound -InterfaceAlias "vEthernet (WSL)" -Action Allow + +ip link add br0 type bridge + +ip link set tap0 master br0 +ip link set dev eth0 down +ip addr flush dev eth0 +ip link set dev eth0 up +ip link set eth0 master br0 + +ip link set dev br0 up + +sudo iptables -A FORWARD --in-interface enp5s0 --out-interface tun0 -j ACCEPT +sudo iptables -t nat -A POSTROUTING --out-interface tun0 -j MASQUERADE \ No newline at end of file diff --git a/examples/http.c b/examples/http.c new file mode 100644 index 0000000..f5c79d8 --- /dev/null +++ b/examples/http.c @@ -0,0 +1,59 @@ +#include + +typedef struct microhttp_server_t http_server_t; +http_server_t *microhttp_server_create(microtcp_t *mtcp, uint16_t port); +void microhttp_server_destroy(http_server_t *server); +void microhttp_server_serve(microhttp_server_t *server, void *data, void (*callback)(void*, microhttp_request_t*)); + +struct microhttp_server_t { + microtcp_t *tcp; + microtcp_listener_t *listener; +}; + +microhttp_server_t *microhttp_server_create(microtcp_t *mtcp, uint16_t port) +{ + microhttp_server_t *server = malloc(sizeof(microhttp_server_t)); + if (!server) + return NULL; + + microtcp_listener_t *listener = microtcp_listener_create(mtcp, port); + if (!listener) { + free(server); + return NULL; + } + + server->mtcp = mtcp; + server->listener = listener; + return server; +} + +void microhttp_server_serve(microhttp_server_t *server, void *data, void (*callback)(void*, microhttp_request_t*)) +{ + char buffer[65536]; + + while (1) { + + microtcp_socket_t *socket = microtcp_listener_accept(server->listener); + if (!socket) + continue; + + int num = microtcp_socket_recv(socket, buffer, sizeof(buffer)); + if (num >= 0) { + hp_error_t error; + hp_request_t request; + if (!hp_parse(buffer, num, &request, &error)) { + .. + } else { + .. + } + } + + microtcp_socket_destroy(socket); + } +} + +void microhttp_server_destroy(microhttp_server_t *server) +{ + microtcp_listener_destroy(server->listener); + free(server); +} diff --git a/examples/main.c b/examples/main.c new file mode 100644 index 0000000..bf9b95d --- /dev/null +++ b/examples/main.c @@ -0,0 +1,49 @@ +#include "net.h" + +static size_t send_callback() +{ +} + +int tun_fd; + +static size_t recv_callback(void *context, void *dst, size_t len) +{ + return read(tun_fd, dst, len); +} + +int main(void) +{ + net_t net; + net_init(&net, ip, mac, NULL, send_callback, recv_callback); + net_spawn_thread(&net); + + uint16_t port = 8080; + + net_listener_t *listener = net_listener_create(&net, port); + if (listener == NULL) { + fprintf(stderr, "Failed to start listening\n"); + net_free(&net); + return -1; + } + + while (1) { + + net_socket_t *client = net_listener_accept(listener); + + if (client == NULL) + continue; + + char message[1024]; + size_t messlen; + + messlen = net_socket_recv(client, message, sizeof(message)); + net_socket_send(client, "echo: ", sizeof("echo: ")); + net_socket_send(client, message, messlen); + + net_socket_destroy(client); + } + + net_listener_destroy(listener); + net_free(&net); + return 0; +} \ No newline at end of file diff --git a/examples/test.c b/examples/test.c new file mode 100644 index 0000000..ca8d570 --- /dev/null +++ b/examples/test.c @@ -0,0 +1,26 @@ +#include "mutcp.h" + +int main(void) +{ + mutcp_t state; + mutcp_create(&state); + + mutcp_listener_socket_t *listener = + mutcp_listener_socket_create(&state, 8080); + + while (1) { + mutcp_socket_t *socket = + mutcp_listener_socket_accept(listener); + + char buffer[1024]; + size_t num = mutcp_socket_read(socket, buffer, sizeof(buffer)); + mutcp_socket_write(socket, "echo: ", 6); + mutcp_socket_write(socket, buffer, num); + + mutcp_socket_destroy(socket); + } + + mutcp_listener_socket_destroy(listener); + mutcp_destroy(&state); + return 0; +} diff --git a/include/microtcp.h b/include/microtcp.h new file mode 100644 index 0000000..796dac9 --- /dev/null +++ b/include/microtcp.h @@ -0,0 +1,57 @@ + +#include +#include +#include +#include + +typedef struct microtcp_t microtcp_t; +typedef struct microtcp_socket_t microtcp_socket_t; + +#define MICROTCP_MAX_BUFFERS 8 +#define MICROTCP_MAX_SOCKETS 32 + +typedef struct { + uint8_t data[6]; +} microtcp_mac_t; + +typedef uint32_t microtcp_ip_t; + +typedef enum { + + MICROTCP_ERRCODE_NONE = 0, + + // Returned by microtcp_open and microtcp_accept + MICROTCP_ERRCODE_SOCKETLIMIT, + + // Returned by microtcp_open + MICROTCP_ERRCODE_TCPERROR, + MICROTCP_ERRCODE_BADCONDVAR, + + // Returned by microtcp_accept + MICROTCP_ERRCODE_NOTLISTENER, + MICROTCP_ERRCODE_CANTBLOCK, + MICROTCP_ERRCODE_NOTHINGTOACCEPT, + + // Returned by microtcp_recv and microtcp_send + MICROTCP_ERRCODE_NOTCONNECTION, + +} microtcp_errcode_t; + +typedef struct { + void *data; + void (*free)(void *data); + int (*send)(void *data, const void *src, size_t len); + int (*recv)(void *data, void *dst, size_t len); +} microtcp_callbacks_t; + +microtcp_t *microtcp_create(); +microtcp_t *microtcp_create_using_callbacks(microtcp_ip_t ip, microtcp_mac_t mac, microtcp_callbacks_t callbacks); +void microtcp_destroy(microtcp_t *mtcp); +const char *microtcp_strerror(microtcp_errcode_t errcode); +microtcp_socket_t *microtcp_open(microtcp_t *mtcp, uint16_t port, microtcp_errcode_t *errcode); +microtcp_socket_t *microtcp_accept(microtcp_socket_t *socket, bool no_block, microtcp_errcode_t *errcode); +void microtcp_close(microtcp_socket_t *socket); +size_t microtcp_send(microtcp_socket_t *socket, const void *src, size_t len, microtcp_errcode_t *errcode); +size_t microtcp_recv(microtcp_socket_t *socket, void *dst, size_t len, microtcp_errcode_t *errcode); +void microtcp_step(microtcp_t *mtcp); +void microtcp_process_packet(microtcp_t *mtcp, const void *packet, size_t len); \ No newline at end of file diff --git a/makefile b/makefile new file mode 100644 index 0000000..5a7be94 --- /dev/null +++ b/makefile @@ -0,0 +1,22 @@ +all: loop test_net test_arp report_arp_cov + +loop: + gcc src/arp.c src/ip.c src/icmp.c src/tcp.c src/microtcp.c src/microtcp_linux.c test/loop.c -o loop -Wall -Wextra -g -DARP_DEBUG -DMICROTCP_DEBUG -DIP_DEBUG -DICMP_DEBUG -DTCP_DEBUG -DMICROTCP_BACKGROUND_THREAD -DMICROTCP_LINUX -pthread -Iinclude/ -fsanitize=thread + +test_arp: + gcc src/arp.c test/test_arp.c test/test_arp_util.c -o test_arp -Wall -Wextra -g -Iinclude/ + +test_arp_cov: + gcc src/arp.c test/test_arp.c test/test_arp_util.c -o test_arp_cov -Wall -Wextra -g -fprofile-arcs -ftest-coverage -lgcov + +report_arp_cov: test_arp_cov + ./test_arp_cov + lcov --capture --directory . --output-file coverage.info --rc lcov_branch_coverage=1 + genhtml coverage.info --output-directory report_arp_cov --rc lcov_branch_coverage=1 + rm *.gcda *.gcno coverage.info + +clean: + rm -f *.gcda *.gcno coverage.info + rm -f test_arp test_arp_cov + rm -f loop + rm -rf report_arp_cov \ No newline at end of file diff --git a/src/arp.c b/src/arp.c new file mode 100644 index 0000000..32b43d1 --- /dev/null +++ b/src/arp.c @@ -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 " +#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 +#include +#include "arp.h" + +#ifdef ARP_DEBUG +#include +#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; +} \ No newline at end of file diff --git a/src/arp.h b/src/arp.h new file mode 100644 index 0000000..33ce781 --- /dev/null +++ b/src/arp.h @@ -0,0 +1,98 @@ +#include +#include +#include +#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); \ No newline at end of file diff --git a/src/defs.h b/src/defs.h new file mode 100644 index 0000000..3d02022 --- /dev/null +++ b/src/defs.h @@ -0,0 +1,20 @@ +#ifndef MICROTCP_DEFS_H +#define MICROTCP_DEFS_H +#include +#include // 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 */ \ No newline at end of file diff --git a/src/icmp.c b/src/icmp.c new file mode 100644 index 0000000..5e58bf4 --- /dev/null +++ b/src/icmp.c @@ -0,0 +1,113 @@ +#include +#include +#include "icmp.h" + +#ifdef ICMP_DEBUG +#include +#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; + } +} \ No newline at end of file diff --git a/src/icmp.h b/src/icmp.h new file mode 100644 index 0000000..731ed97 --- /dev/null +++ b/src/icmp.h @@ -0,0 +1,14 @@ +#include +#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); diff --git a/src/ip.c b/src/ip.c new file mode 100644 index 0000000..bc24a9d --- /dev/null +++ b/src/ip.c @@ -0,0 +1,221 @@ +#include +#include // ntohs() +#include "ip.h" + +#ifdef IP_DEBUG +#include +#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); +} diff --git a/src/ip.h b/src/ip.h new file mode 100644 index 0000000..715282c --- /dev/null +++ b/src/ip.h @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#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)); diff --git a/src/microtcp.c b/src/microtcp.c new file mode 100644 index 0000000..595a96a --- /dev/null +++ b/src/microtcp.c @@ -0,0 +1,758 @@ +#include // time() +#include +#include // strerror() +#include +#include +#include + +#include "ip.h" +#include "arp.h" +#include "tcp.h" +#include + +#ifdef MICROTCP_BACKGROUND_THREAD +#include +#endif + +#ifdef MICROTCP_DEBUG +#include +#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; +} diff --git a/src/microtcp_linux.c b/src/microtcp_linux.c new file mode 100644 index 0000000..3e453e9 --- /dev/null +++ b/src/microtcp_linux.c @@ -0,0 +1,181 @@ +#ifdef MICROTCP_LINUX + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 */ \ No newline at end of file diff --git a/src/tcp.c b/src/tcp.c new file mode 100644 index 0000000..aadc24b --- /dev/null +++ b/src/tcp.c @@ -0,0 +1,468 @@ +#include +#include +#include "tcp.h" + +#ifdef TCP_DEBUG +#include +#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 + +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; +} diff --git a/src/tcp.h b/src/tcp.h new file mode 100644 index 0000000..ad9052e --- /dev/null +++ b/src/tcp.h @@ -0,0 +1,96 @@ +#include +#include +#include +#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); diff --git a/test/loop.c b/test/loop.c new file mode 100644 index 0000000..9d94cae --- /dev/null +++ b/test/loop.c @@ -0,0 +1,56 @@ +#include +#include + +int main(void) +{ + microtcp_errcode_t errcode; + + microtcp_t *mtcp = microtcp_create(); + if (mtcp == NULL) + return -1; + + uint16_t port = 80; + microtcp_socket_t *server = microtcp_open(mtcp, port, &errcode); + if (errcode) { + fprintf(stderr, "Error: %s\n", microtcp_strerror(errcode)); + microtcp_destroy(mtcp); + return -1; + } + assert(server); + + fprintf(stderr, "Listening on port %d\n", port); + + while (1) { + fprintf(stderr, "About to accept\n"); + microtcp_socket_t *client = microtcp_accept(server, false, &errcode); + if (errcode && errcode != MICROTCP_ERRCODE_NOTHINGTOACCEPT) { + fprintf(stderr, "Error: %s\n", microtcp_strerror(errcode)); + break; + } + + fprintf(stderr, "Accepted a connection\n"); + + char buffer[1024]; + size_t num = microtcp_recv(client, buffer, sizeof(buffer), &errcode); + if (errcode) { + fprintf(stderr, "Error: %s\n", microtcp_strerror(errcode)); + goto handled; + } + microtcp_send(client, "echo: ", 6, &errcode); + if (errcode) { + fprintf(stderr, "Error: %s\n", microtcp_strerror(errcode)); + goto handled; + } + microtcp_send(client, buffer, num, &errcode); + if (errcode) { + fprintf(stderr, "Error: %s\n", microtcp_strerror(errcode)); + goto handled; + } +handled: + microtcp_close(client); + } + + microtcp_close(server); + microtcp_destroy(mtcp); + return 0; +} diff --git a/test/test_arp.c b/test/test_arp.c new file mode 100644 index 0000000..ee347c5 --- /dev/null +++ b/test/test_arp.c @@ -0,0 +1,161 @@ +#include +#include +#include "test_arp_util.h" + +/* + WHITE BOX TEST CASES + + A) Peer requests host's MAC given its IP and host + replies. + + B) Peer requests host's non MAC level 2 address + given its IP and host doesn't reply because it + only supports MAC. + + C) Peer requests host's MAC address given its non + IP level 3 address and host doesn't reply + because it only supports IP. + + D) Peer requests host's MAC given its IP, but the + MAC address length field isn't 6, therefore + host doesn't reply. + + E) Peer requests host's MAC given its IP, but the + IP address length field isn't 4, therefore host + doesn't reply. + + F) Peer sends a request/reply that doesn't refer + to host and is from a sender with IP never seen + by the host (no entry in the translation table). + It's expected that no entry is added to the + translation table. + + G) Peer sends a request/reply that doesn't refer + to host but is from a sender with IP already + in the ARP translation table, therefore is + expected that host updates the entry. + + H) Program queries the ARP module for a MAC that + isn't cached, therefore an ARP request is + expected to be generated and, when replied to, + the ARP module is expected to resolve the + program's query. + + I) Program queries the ARP module for a MAC that's + cached, therefore the ARP module is expected to + resolve it without sending packets. +*/ + +static arp_testcase_result_t test_000(char *msg, size_t msgmax) +{ + char host_ip[4] = {0xc0, 0xa8, 0x01, 0x05}; + char host_mac[6] = {0xcc, 0x6b, 0x1e, 0x13, 0xa8, 0x93}; + + arp_testcase_t testcase; + arp_testcase_init(&testcase, host_mac, host_ip); + + arp_testcase_send(&testcase, (char[]) { + 0x00, 0x01, // hardware_type=ethernet + 0x08, 0x00, // protocol_type=ip + 0x06, // hardware_len=6 + 0x04, // protocol_len=4 + 0x00, 0x01, // operation_type=request + 0xbc, 0x15, 0xac, 0x29, 0xe5, 0x61, // sender MAC + 0xc0, 0xa8, 0x01, 0x01, // sender IP + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // target MAC (empty) + 0xc0, 0xa8, 0x01, 0x05, // target IP + }); + + arp_testcase_recv(&testcase, (char[]) { + 0x00, 0x01, // hardware_type=ethernet + 0x08, 0x00, // protocol_type=ip + 0x06, // hardware_len=6 + 0x04, // protocol_len=4 + 0x00, 0x02, // operation_type=reply + 0xcc, 0x6b, 0x1e, 0x13, 0xa8, 0x93, // sender MAC + 0xc0, 0xa8, 0x01, 0x05, // sender IP + 0xbc, 0x15, 0xac, 0x29, 0xe5, 0x61, // target MAC + 0xc0, 0xa8, 0x01, 0x01, // target IP + }); + + arp_testcase_result_t result = + arp_testcase_run(testcase, msg, msgmax); + + arp_testcase_free(&testcase); + return result; +} + + +static arp_testcase_result_t test_001(char *msg, size_t msgmax) +{ + /* This testcase simulates the host receiving + * an ARP request not associated to it, therefore + * the ARP module is expected to not reply. + */ + + char host_ip[4] = {0xc0, 0xa8, 0x01, 0x05}; + char host_mac[6] = {0xcc, 0x6b, 0x1e, 0x13, 0xa8, 0x93}; + + arp_testcase_t testcase; + arp_testcase_init(&testcase, host_mac, host_ip); + + arp_testcase_send(&testcase, (char[]) { + 0x00, 0x01, // hardware_type=ethernet + 0x08, 0x00, // protocol_type=ip + 0x06, // hardware_len=6 + 0x04, // protocol_len=4 + 0x00, 0x01, // operation_type=request + 0xbc, 0x15, 0xac, 0x29, 0xe5, 0x61, // sender MAC + 0xc0, 0xa8, 0x01, 0x01, // sender IP + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // target MAC (empty) + 0xc0, 0xa8, 0x01, 0x10, // target IP (different to the host's) + }); + + arp_testcase_result_t result = + arp_testcase_run(testcase, msg, msgmax); + + arp_testcase_free(&testcase); + return result; +} + +typedef arp_testcase_result_t (*arp_testcase_routine_t)(char*, size_t); + +int main(void) +{ + static const arp_testcase_routine_t routines[] = { + test_000, + test_001, + NULL, + }; + + size_t passed = 0; + size_t failed = 0; + size_t aborted = 0; + + for (size_t i = 0; routines[i]; i++) { + + char message[1024]; + arp_testcase_result_t result = routines[i](message, sizeof(message)); + + switch (result) { + case ARP_TESTCASE_PASSED: + fprintf(stdout, "Test %ld ... PASSED\n", i); + passed++; + break; + + case ARP_TESTCASE_FAILED: + fprintf(stdout, "Test %ld ... FAILED: %s\n", i, message); + failed++; + break; + + case ARP_TESTCASE_ABORTED: + fprintf(stdout, "Test %ld ... ABORTED: %s\n", i, message); + aborted++; + break; + } + } + + fprintf(stdout, "SUMMARY: %ld passed, %ld failed and %ld aborted\n", + passed, failed, aborted); + return 0; +} \ No newline at end of file diff --git a/test/test_arp_util.c b/test/test_arp_util.c new file mode 100644 index 0000000..ed651a5 --- /dev/null +++ b/test/test_arp_util.c @@ -0,0 +1,146 @@ +#include +#include +#include "test_arp_util.h" + +void arp_testcase_init(arp_testcase_t *tcase, + const char mac[static 6], + const char ip[static 4]) +{ + memcpy(&tcase->self_ip, ip, 4); + memcpy(&tcase->self_mac, mac, 6); + tcase->failed = false; + tcase->count = 0; +} + +void arp_testcase_free(arp_testcase_t *tcase) +{ + (void) tcase; +} + +void arp_testcase_send(arp_testcase_t *tcase, const char data[static sizeof(arp_packet_t)]) +{ + if (tcase->failed) + return; + + if (tcase->count == ARP_TESTCASE_MAX_PACKETS) { + tcase->failed = true; + return; + } + + arp_testcase_packet_t *packet = tcase->packets + tcase->count; + packet->sender = ARP_TESTCASE_SENDER_PEER; + packet->data = data; + + tcase->count++; +} + +void arp_testcase_recv(arp_testcase_t *tcase, const char data[static sizeof(arp_packet_t)]) +{ + if (tcase->failed) + return; + + if (tcase->count == ARP_TESTCASE_MAX_PACKETS) { + tcase->failed = true; + return; + } + + arp_testcase_packet_t *packet = tcase->packets + tcase->count; + packet->sender = ARP_TESTCASE_SENDER_HOST; + packet->data = data; + + tcase->count++; +} + +typedef struct { + char *msg; + size_t msgmax; + const arp_packet_t *output; + const arp_testcase_t *tcase; + size_t cursor; + bool output_as_expected; + bool aborted_while_checking_sent_packets; +} testcase_contex_t; + +static void send_packet(void *data, mac_address_t dest) +{ + (void) dest; // Is this ok? + + testcase_contex_t *context = data; + + context->aborted_while_checking_sent_packets = false; + + if (context->cursor == context->tcase->count) { + snprintf(context->msg, context->msgmax, "ARP module sent an unexpected packet"); + context->output_as_expected = false; + return; + } + + const arp_testcase_packet_t *packet = context->tcase->packets + context->cursor; + + if (packet->sender != ARP_TESTCASE_SENDER_HOST) { + snprintf(context->msg, context->msgmax, "ARP module sent an unexpected packet"); + context->output_as_expected = false; + return; + } + context->cursor++; + + if (!memcmp(context->output, packet->data, sizeof(arp_packet_t))) + context->output_as_expected = true; + else { + snprintf(context->msg, context->msgmax, "ARP module sent a different packet than expected"); + context->output_as_expected = false; + } +} + +arp_testcase_result_t arp_testcase_run(arp_testcase_t tcase, char *msg, size_t msgmax) +{ + if (tcase.failed) + return ARP_TESTCASE_ABORTED; + + arp_packet_t output; + + testcase_contex_t context = { + .output = &output, + .tcase = &tcase, + .cursor = 0, + .msg = msg, + .msgmax = msgmax, + }; + + if (msgmax > 0) + msg[0] = '\0'; + + arp_state_t state; + arp_init(&state, tcase.self_ip, tcase.self_mac, &context, send_packet); + arp_change_output_buffer(&state, &output, sizeof(output)); + + while (context.cursor < tcase.count) { + + arp_testcase_packet_t *packet = tcase.packets + context.cursor++; + + if (packet->sender != ARP_TESTCASE_SENDER_PEER) { + snprintf(msg, msgmax, "ARP module didn't send packet"); + return ARP_TESTCASE_FAILED; + } + + // Initialize these + context.output_as_expected = true; + context.aborted_while_checking_sent_packets = false; + + arp_process_result_t status = arp_process_packet(&state, packet->data, sizeof(arp_packet_t)); + + (void) status; // Not useful yet + + // Before this point the arp_process_packet will have + // sent some packets that were validated in the send_packet + // callback. If the testcase didn't fail, the next + // packet in the list will be sent by the peer. + if (context.aborted_while_checking_sent_packets) + return ARP_TESTCASE_ABORTED; + if (!context.output_as_expected) + return ARP_TESTCASE_FAILED; + } + + arp_free(&state); + return ARP_TESTCASE_PASSED; +} \ No newline at end of file diff --git a/test/test_arp_util.h b/test/test_arp_util.h new file mode 100644 index 0000000..e7dee5b --- /dev/null +++ b/test/test_arp_util.h @@ -0,0 +1,36 @@ +#include +#include "../src/arp.h" + +#define ARP_TESTCASE_MAX_PACKETS 1024 + +typedef enum { + ARP_TESTCASE_SENDER_HOST, + ARP_TESTCASE_SENDER_PEER, +} arp_testcase_packet_sender_t; + +typedef struct { + arp_testcase_packet_sender_t sender; + const char *data; +} arp_testcase_packet_t; + +typedef struct { + bool failed; + + ip_address_t self_ip; + mac_address_t self_mac; + + size_t count; + arp_testcase_packet_t packets[ARP_TESTCASE_MAX_PACKETS]; +} arp_testcase_t; + +typedef enum { + ARP_TESTCASE_PASSED, + ARP_TESTCASE_FAILED, + ARP_TESTCASE_ABORTED, +} arp_testcase_result_t; + +void arp_testcase_init(arp_testcase_t *tcase, const char mac[static 6], const char ip[static 4]); +void arp_testcase_free(arp_testcase_t *tcase); +void arp_testcase_send(arp_testcase_t *tcase, const char data[static sizeof(arp_packet_t)]); +void arp_testcase_recv(arp_testcase_t *tcase, const char data[static sizeof(arp_packet_t)]); +arp_testcase_result_t arp_testcase_run(arp_testcase_t tcase, char *msg, size_t msgmax);