first commit

This commit is contained in:
Francesco Cozzuto
2023-03-20 09:43:11 +01:00
commit 00e68532d1
22 changed files with 3401 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
*.pcapng
loop
test_arp
test_arp_cov
report_arp_cov
+14
View File
@@ -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
+59
View File
@@ -0,0 +1,59 @@
#include <microtcp.h>
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);
}
+49
View File
@@ -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;
}
+26
View File
@@ -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;
}
+57
View File
@@ -0,0 +1,57 @@
#include <stddef.h>
#include <stdint.h>
#include <assert.h>
#include <stdbool.h>
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);
+22
View File
@@ -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
+734
View File
@@ -0,0 +1,734 @@
/* Il protocollo ARP (Address Resolution Protocol)
* permette di tradurre gli indirizzi di livello
* rete (come IP) a quelli del livello inferiore
* data-link (come ethernet).
* Per grandi linee, i messaggi ARP possono essere
* REQUEST o REPLY. Quando un host vuole comunicare
* con un altro host dato il suo IP (o qualsiasi
* indirizzo di livello 3), manda un messaggio di
* REQUEST in broadcast (MAC di destinazione
* ff:ff:ff:ff:ff:ff) contenente l'indirizzo di
* livello 3 del quale si vuole conoscere quello di
* livello 2. Ciascun host della rete riceve il
* messaggio e controlla se la richiesta è relativa
* al proprio IP. Se il controllo risulta positivo
* risponde con un messaggio di REPLY contenente
* il proprio MAC. Il messaggio di REPLY, a differenza
* della REQUEST è un unicast dato che è noto il
* destinatario.
*
* Il messaggio ARP è grande 28 byte ed, indipendentemente
* dal tipo di richiesta, ha questa struttura:
*
* (16 bits per row)
* +-----------------------------------+
* 0 | hardware_type |
* +-----------------------------------+
* 2 | protocol_type |
* +-----------------+-----------------+
* 4 | hardware_length | protocol_length |
* +-----------------+-----------------+
* 6 | operation_type |
* +-----------------------------------+
* 8 | sender_hardware_address |
* +- - - - - - - - - - - - - - - - - -+
* | |
* +- - - - - - - - - - - - - - - - - -+
* | |
* +-----------------------------------+
* 14 | sender_protocol_address |
* +- - - - - - - - - - - - - - - - - -+
* | |
* +-----------------------------------+
* 18 | target_hardware_address |
* +- - - - - - - - - - - - - - - - - -+
* | |
* +- - - - - - - - - - - - - - - - - -+
* | |
* +-----------------------------------+
* 24 | target_protocol_address |
* +- - - - - - - - - - - - - - - - - -+
* | |
* +-----------------------------------+
*
* I campi hardware_type e protocol_type indicano
* il protocollo di livello 2 e quello di livello 3.
* Nel caso di IP su Ethernet si ha hardware_type=1
* e protocol_type=0x800.
*
* I campi hardware_length e protocol_length
* indicano la dimensione in byte degli indirizzi
* dei due protocolli. Per IP e Ethernet questi
* sono ridondanti dato che ogni indirizzo Ethernet
* è di 6 byte ed ogni indirizzo IP di 4.
*
* Il campo operation_type indica la finalità del
* messaggio. Può essere uno di:
* ARP REQUEST -> operation_type=1
* ARP REPLY -> operation_type=2
* RARP REQUEST -> operation_type=3
* RARP REPLY -> operation_type=4
* (possiamo ignorare RARP per ora)
*
* Il campo sender_hardware_address e sender_protocol_address
* contengono MAC e IP di chi ha inviato il messaggio.
*
* Il campo target_hardware_address cambia di significato
* a seconda del tipo di operazione:
* ARP REQUEST -> è vuoto, perchè non è noto il MAC di
* destinazione (vogliamo determinarlo)
* ARP REPLY -> indirizzo MAC di chi ha fatto la REQUEST
*
* Il campo target_protocol_address contiene l'indirizzo IP
* di chi ha inviato il messaggio.
*/
/*
struct iphdr
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
unsigned int ihl:4;
unsigned int version:4;
#elif __BYTE_ORDER == __BIG_ENDIAN
unsigned int version:4;
unsigned int ihl:4;
#else
# error "Please fix <bits/endian.h>"
#endif
uint8_t tos;
uint16_t tot_len;
uint16_t id;
uint16_t frag_off;
uint8_t ttl;
uint8_t protocol;
uint16_t check;
uint32_t saddr;
uint32_t daddr;
// The options start here.
};
struct ethhdr {
unsigned char h_dest[ETH_ALEN]; // destination eth addr
unsigned char h_source[ETH_ALEN]; // source ether addr
__be16 h_proto; // packet type ID field
} __attribute__((packed));
*/
#include <stdbool.h>
#include <arpa/inet.h>
#include "arp.h"
#ifdef ARP_DEBUG
#include <stdio.h>
#define ARP_DEBUG_LOG(fmt, ...) fprintf(stderr, "ARP :: " fmt "\n", ## __VA_ARGS__)
#else
#define ARP_DEBUG_LOG(...)
#endif
typedef enum {
ARP_HARDWARE_ETHERNET = 1,
} arp_hardware_type;
typedef enum {
ARP_PROTOCOL_IP = 0x800,
} arp_protocol_type;
typedef enum {
ARP_OPERATION_REQUEST = 1,
ARP_OPERATION_REPLY = 2,
} arp_operation_t;
void arp_change_output_buffer(arp_state_t *state, void *ptr, size_t max)
{
if (max < sizeof(arp_packet_t))
state->output = NULL;
else
state->output = ptr;
}
static void arp_translation_table_seconds_passed(arp_translation_table_t *table, size_t seconds)
{
table->time += seconds;
// Determine all of the elements of the table that have just
// timed out.
//
// The [used_list] contains all of the active table entries
// in a doubly linked list. The first element is referred by
// [table->used_list_head], and the last by [table->used_list_tail].
// The entries are ordered in descending [entry->timeout]
// attribute. The [timeout] attribute indicates the absolute
// time at which the entry will be considered invalid,
// relative to [table->time].
//
// Since the list goes from high to low timeout, if an entry
// at a given point in time isn't timed-out, all of the
// entries that come before it also aren't timed-out.
// Analogously, is an entry in a given point in time is
// timed-out, all of the entries after it are also timed-out.
//
// In general, at any given point in time, the list is made
// of a first half of non-timed-out entries and a second half
// of timed-out entries.
//
// This function needs to remove the timed-out tail of the
// used entries list and add it to the free entry list.
//
// Find from the end of the list the first non-timed-out
// entry. The timed-out elements will be all of the ones
// that come after it.
//
// NOTE: If all of the entries are timed-out or the list is
// empty, the loop will exit with the NULL entry.
arp_translation_table_entry_t *entry = table->used_list_tail;
while (entry && entry->timeout < table->time)
entry = entry->prev;
// First and last element of the timed-out list. We need
// to determine these.
arp_translation_table_entry_t *timeout_list;
arp_translation_table_entry_t *timeout_tail;
if (entry) {
// The iteration didn't end with a NULL cursor, so either
// there are no timed-out elements (in which case the cursor
// is the tail of the list) or there are both timed-out and
// non-timed-out entries.
//
// Either way, the start of the list is [entry->next].
timeout_list = entry->next;
//
// If there are no timed-out entries, the tail of the timed-out
// list must be NULL, else it's the tail of the used list.
timeout_tail = entry->next ? table->used_list_tail : NULL;
//
// The entry becomes the new tail
entry->next = NULL;
table->used_list_tail = entry;
} else {
// If the iteration ended with a NULL cursor, there
// are no valid entries in the list. Either the list
// is all timed-out, or it's empty.
//
// Either way we take the list pointers and make them
// out timed-out list.
timeout_list = table->used_list_head;
timeout_tail = table->used_list_tail;
//
// If the list wasn't empty, we make it so.
table->used_list_head = NULL;
table->used_list_tail = NULL;
}
// Append the timed-out list to the free list
if (timeout_list) {
timeout_list->prev = NULL;
timeout_tail->next = table->free_list;
table->free_list = timeout_list;
}
}
void arp_seconds_passed(arp_state_t *state, size_t seconds)
{
state->time += seconds;
// Scan through all of the timed-out entries
// in the pending request list from the tail
arp_pending_request_t *cursor = state->pending_request_used_tail;
while (cursor && cursor->timeout < state->time)
cursor = cursor->prev;
// Chop off the list of timed out entries
arp_pending_request_t *timeout_list;
arp_pending_request_t *timeout_tail;
if (cursor) {
// Cursor holds the first request that's not timed out,
// therefore all of the entries that come after it are
// now invalid
timeout_list = cursor->next;
timeout_tail = cursor->next ? state->pending_request_used_tail : NULL;
// Now chop off the list
cursor->next = NULL;
state->pending_request_used_tail = cursor;
} else {
// Either the list is empty or all of the requests are
// now invalid.
timeout_list = state->pending_request_used_list;
timeout_tail = state->pending_request_used_tail;
state->pending_request_used_list = NULL;
state->pending_request_used_tail = NULL;
}
// Now walk through the timed out entries and
// run the callback with the timeout status code
arp_pending_request_t *timeout_cursor = timeout_list;
while (timeout_cursor) {
timeout_cursor->callback(timeout_cursor->callback_data, ARP_RESOLUTION_TIMEOUT, MAC_ZERO);
timeout_cursor = timeout_cursor->next;
}
// Now put the timed out entries back in the free
// list (if there are any)
if (timeout_list) {
timeout_list->prev = NULL;
timeout_tail->next = state->pending_request_free_list;
state->pending_request_free_list = timeout_list;
}
arp_translation_table_seconds_passed(&state->table, seconds);
}
static void
arp_translation_table_init(arp_translation_table_t *table)
{
table->time = 0;
table->used_list_head = NULL;
table->used_list_tail = NULL;
table->free_list = table->entries;
for (size_t i = 0; i < ARP_TRANSLATION_TABLE_SIZE-1; i++) {
table->entries[i].prev = NULL;
table->entries[i].next = table->entries + i+1;
}
table->entries[ARP_TRANSLATION_TABLE_SIZE-1].prev = NULL;
table->entries[ARP_TRANSLATION_TABLE_SIZE-1].next = NULL;
}
static void
arp_translation_table_free(arp_translation_table_t *table)
{
(void) table;
}
#ifdef ARP_DEBUG
static bool
arp_translation_table_entry_is_used(arp_translation_table_t *table,
arp_translation_table_entry_t *entry)
{
arp_translation_table_entry_t *cursor = table->used_list_head;
while (cursor) {
if (cursor == entry)
return true;
cursor = cursor->next;
}
return false;
}
static bool
arp_translation_table_entry_is_unlinked(arp_translation_table_t *table,
arp_translation_table_entry_t *entry)
{
return entry->prev == NULL
&& entry->next == NULL
&& table->free_list != entry
&& table->used_list_head != entry
&& table->used_list_tail != entry;
}
#endif
static void
arp_translation_table_unlink_used_entry(arp_translation_table_t *table,
arp_translation_table_entry_t *entry)
{
#ifdef ARP_DEBUG
assert(!arp_translation_table_entry_is_unlinked(table, entry));
#endif
if (entry->prev)
entry->prev->next = entry->next;
else
table->used_list_head = entry->next;
if (entry->next)
entry->next->prev = entry->prev;
else
table->used_list_tail = entry->prev;
entry->prev = NULL;
entry->next = NULL;
#ifdef ARP_DEBUG
assert(arp_translation_table_entry_is_unlinked(table, entry));
#endif
}
static void
arp_translation_table_insert_unlinked_entry_into_used_list(arp_translation_table_t *table,
arp_translation_table_entry_t *entry)
{
#ifdef ARP_DEBUG
assert(arp_translation_table_entry_is_unlinked(table, entry));
assert(!arp_translation_table_entry_is_used(table, entry));
#endif
// Find the first entry with the lower timeout
arp_translation_table_entry_t *cursor = table->used_list_head;
while (cursor && cursor->timeout < entry->timeout)
cursor = cursor->next;
if (cursor) {
// Insert the entry before the cursor position.
entry->prev = cursor->prev;
entry->next = cursor;
if (cursor->prev)
cursor->prev->next = entry;
else
table->used_list_head = entry;
cursor->prev = entry;
} else {
// Either the list is empty or the entry must
// be inserted last.
entry->prev = table->used_list_tail;
entry->next = NULL;
if (table->used_list_tail)
table->used_list_tail->next = entry;
else
table->used_list_head = entry;
table->used_list_tail = entry;
}
#ifdef ARP_DEBUG
assert(!arp_translation_table_entry_is_unlinked(table, entry));
assert(arp_translation_table_entry_is_used(table, entry));
#endif
}
static void
arp_translation_table_free_least_recently_used_entry(arp_translation_table_t *table)
{
arp_translation_table_entry_t *entry = table->used_list_tail;
if (entry) {
#ifdef ARP_DEBUG
assert(!arp_translation_table_entry_is_unlinked(table, entry));
#endif
arp_translation_table_unlink_used_entry(table, entry);
#ifdef ARP_DEBUG
assert(arp_translation_table_entry_is_unlinked(table, entry));
#endif
// Push the entry to the free list
entry->next = table->free_list;
table->free_list = entry;
#ifdef ARP_DEBUG
assert(!arp_translation_table_entry_is_unlinked(table, entry));
#endif
}
}
static arp_translation_table_entry_t*
arp_translation_table_find_entry_by_ip(arp_translation_table_t *table,
ip_address_t ip)
{
arp_translation_table_entry_t *entry = table->used_list_head;
while (entry) {
if (entry->ip == ip)
return entry;
entry = entry->next;
}
return NULL;
}
static bool arp_translation_table_find_mac_by_ip(arp_translation_table_t *table,
ip_address_t ip, mac_address_t *mac)
{
arp_translation_table_entry_t *entry =
arp_translation_table_find_entry_by_ip(table, ip);
if (entry)
*mac = entry->mac;
return !!entry;
}
static arp_translation_table_entry_t*
arp_translation_table_pop_free_entry(arp_translation_table_t *table)
{
arp_translation_table_entry_t *entry = table->free_list;
if (entry)
table->free_list = entry->next;
return entry;
}
static void
arp_translation_table_initialize_entry(arp_translation_table_entry_t *entry,
mac_address_t mac, ip_address_t ip,
uint64_t timeout)
{
entry->mac = mac;
entry->ip = ip;
entry->timeout = timeout;
entry->prev = NULL;
entry->next = NULL;
}
static void
arp_translation_table_insert_or_update(arp_translation_table_t *table,
mac_address_t mac, ip_address_t ip,
uint64_t timeout)
{
arp_translation_table_entry_t *entry =
arp_translation_table_find_entry_by_ip(table, ip);
if (entry) {
entry->timeout = table->time + timeout; // Refresh timeout
arp_translation_table_unlink_used_entry(table, entry);
} else {
entry = arp_translation_table_pop_free_entry(table);
if (!entry) {
arp_translation_table_free_least_recently_used_entry(table);
entry = arp_translation_table_pop_free_entry(table);
}
assert(entry);
arp_translation_table_initialize_entry(entry, mac, ip, table->time + timeout);
}
arp_translation_table_insert_unlinked_entry_into_used_list(table, entry);
}
static bool
arp_translation_table_update(arp_translation_table_t *table,
mac_address_t mac, ip_address_t ip,
uint64_t timeout)
{
arp_translation_table_entry_t *entry =
arp_translation_table_find_entry_by_ip(table, ip);
if (entry) {
arp_translation_table_unlink_used_entry(table, entry);
arp_translation_table_initialize_entry(entry, mac, ip, table->time + timeout);
arp_translation_table_insert_unlinked_entry_into_used_list(table, entry);
}
return !!entry;
}
void arp_init(arp_state_t *state, ip_address_t ip, mac_address_t mac,
void *send_data, void (*send)(void*, mac_address_t))
{
state->time = 0;
state->request_timeout = 1;
state->cache_timeout = 10;
state->output = NULL;
state->send_data = send_data;
state->send = send;
state->self_ip = ip;
state->self_mac = mac;
arp_translation_table_init(&state->table);
state->pending_request_used_list = NULL;
state->pending_request_used_tail = NULL;
state->pending_request_free_list = state->pending_request_pool;
for (size_t i = 0; i < ARP_MAX_PENDING_REQUESTS; i++)
state->pending_request_pool[i].next = state->pending_request_pool + i+1;
state->pending_request_pool[ARP_MAX_PENDING_REQUESTS-1].next = NULL;
}
void arp_free(arp_state_t *state)
{
arp_translation_table_free(&state->table);
}
static void append_pending_request_to_used_list(arp_state_t *state, arp_pending_request_t *pending_request)
{
arp_pending_request_t *cursor = state->pending_request_used_list;
// Find the first pending request in the list
// with a lower timeout and insert the request
// before it.
while (cursor && cursor->timeout > pending_request->timeout)
cursor = cursor->next;
if (cursor) {
pending_request->prev = cursor->prev;
pending_request->next = cursor;
if (cursor->prev)
cursor->prev->next = pending_request;
else
state->pending_request_used_list = pending_request;
cursor->prev = pending_request;
} else {
// Insert the request in the tail of the list
pending_request->prev = state->pending_request_used_tail;
pending_request->next = NULL;
if (state->pending_request_used_tail)
state->pending_request_used_tail->next = pending_request;
else
state->pending_request_used_list = pending_request;
state->pending_request_used_tail = pending_request;
}
}
void arp_resolve_mac(arp_state_t *state, ip_address_t ip, void *userp, void (*callback)(void*, arp_resolution_status_t, mac_address_t))
{
bool found_mac_locally;
mac_address_t mac;
if (state->self_ip == ip) {
mac = state->self_mac;
found_mac_locally = true;
} else
found_mac_locally = arp_translation_table_find_mac_by_ip(&state->table, ip, &mac);
if (found_mac_locally)
callback(userp, ARP_RESOLUTION_OK, mac);
else {
// MAC isn't in the translation table.
// We need to make an ARP REQUEST
arp_pending_request_t *pending_request = state->pending_request_free_list;
if (pending_request == NULL) {
callback(userp, ARP_RESOLUTION_FAILED, MAC_ZERO);
return;
}
state->pending_request_free_list = pending_request->next;
pending_request->ip = ip;
pending_request->timeout = state->time + state->request_timeout;
pending_request->callback = callback;
pending_request->callback_data = userp;
pending_request->prev = NULL;
pending_request->next = NULL;
append_pending_request_to_used_list(state, pending_request);
arp_packet_t *packet = state->output;
packet->hardware_type = htons(ARP_HARDWARE_ETHERNET);
packet->protocol_type = htons(ARP_PROTOCOL_IP);
packet->hardware_len = 6;
packet->protocol_len = 4;
packet->operation_type = htons(ARP_OPERATION_REQUEST);
packet->sender_hardware_address = state->self_mac;
packet->sender_protocol_address = state->self_ip;
packet->target_hardware_address = MAC_ZERO; // This is what we're trying to find
packet->target_protocol_address = ip;
ARP_DEBUG_LOG("Sending out ARP request to resolve MAC");
state->send(state->send_data, MAC_BROADCAST);
}
}
static void
try_resolving_pending_requests(arp_state_t *state, ip_address_t ip, mac_address_t mac)
{
// NOTE: Could try resolving pending requests from
// the tail of the list instead of the head
// since the tail entries have been waiting
// longer. I think we can assume the older
// entries have higher chances of being resolved.
arp_pending_request_t *pending_request = state->pending_request_used_list;
arp_pending_request_t *prev = NULL;
while (pending_request) {
arp_pending_request_t *next = pending_request->next;
if (pending_request->ip == ip) {
pending_request->callback(pending_request->callback_data, ARP_RESOLUTION_OK, mac);
pending_request->next = state->pending_request_free_list;
state->pending_request_free_list = pending_request;
if (prev)
prev->next = next;
else
state->pending_request_used_list = next;
if (next)
next->prev = prev;
else
state->pending_request_used_tail = prev;
} else
prev = pending_request;
pending_request = next;
}
}
arp_process_result_t arp_process_packet(arp_state_t *state, const void *packet, size_t len)
{
if (len != sizeof(arp_packet_t))
return ARP_PROCESS_RESULT_INVALID;
const arp_packet_t *packet2 = packet;
if (packet2->hardware_type != htons(ARP_HARDWARE_ETHERNET)) {
/* Level 2 protocol not supported */
ARP_DEBUG_LOG("Hardware type %d not supported", packet2->hardware_type);
return ARP_PROCESS_RESULT_HWARENOTSUPP;
}
if (packet2->protocol_type != htons(ARP_PROTOCOL_IP)) {
/* Level 3 protocol not supported */
ARP_DEBUG_LOG("Protocol type %d not supported", packet2->protocol_type);
return ARP_PROCESS_RESULT_PROTONOTSUPP;
}
if (packet2->hardware_len != 6 || packet2->protocol_len != 4) {
/* Invalid fields */
ARP_DEBUG_LOG("Invalid hardware or protocol address size %d or %d (expected %d and %d)", packet2->hardware_len, packet2->protocol_len, 6, 4);
return ARP_PROCESS_RESULT_INVALID;
}
bool merge = arp_translation_table_update(&state->table, packet2->sender_hardware_address,
packet2->sender_protocol_address, state->cache_timeout);
if (packet2->target_protocol_address == state->self_ip) {
if (!merge) {
arp_translation_table_insert_or_update(&state->table, packet2->sender_hardware_address,
packet2->sender_protocol_address, state->cache_timeout);
try_resolving_pending_requests(state, packet2->sender_protocol_address,
packet2->sender_hardware_address);
}
if (packet2->operation_type == htons(ARP_OPERATION_REQUEST)) {
// Generate the ARP REPLY
arp_packet_t *response = state->output;
response->hardware_type = packet2->hardware_type;
response->protocol_type = packet2->protocol_type;
response->hardware_len = packet2->hardware_len;
response->protocol_len = packet2->protocol_len;
response->operation_type = htons(ARP_OPERATION_REPLY);
response->sender_hardware_address = state->self_mac;
response->sender_protocol_address = state->self_ip;
response->target_hardware_address = packet2->sender_hardware_address;
response->target_protocol_address = packet2->sender_protocol_address;
ARP_DEBUG_LOG("Sending reply");
state->send(state->send_data, packet2->sender_hardware_address);
}
} else {
ARP_DEBUG_LOG("Request not for me");
}
return ARP_PROCESS_RESULT_OK;
}
+98
View File
@@ -0,0 +1,98 @@
#include <assert.h>
#include <stdint.h>
#include <stddef.h>
#include "defs.h"
#define ARP_MAX_PENDING_REQUESTS 32
#define ARP_TRANSLATION_TABLE_SIZE 128
typedef enum {
ARP_RESOLUTION_OK,
ARP_RESOLUTION_FAILED,
ARP_RESOLUTION_TIMEOUT,
} arp_resolution_status_t;
typedef struct arp_translation_table_entry_t arp_translation_table_entry_t;
struct arp_translation_table_entry_t {
arp_translation_table_entry_t *prev;
arp_translation_table_entry_t *next;
mac_address_t mac;
ip_address_t ip;
uint64_t timeout;
};
typedef struct {
uint64_t time;
arp_translation_table_entry_t *used_list_head;
arp_translation_table_entry_t *used_list_tail;
arp_translation_table_entry_t *free_list;
arp_translation_table_entry_t entries[ARP_TRANSLATION_TABLE_SIZE];
} arp_translation_table_t;
typedef struct arp_pending_request_t arp_pending_request_t;
struct arp_pending_request_t {
arp_pending_request_t *prev;
arp_pending_request_t *next;
ip_address_t ip;
uint64_t timeout;
void *callback_data;
void (*callback)(void*, arp_resolution_status_t status, mac_address_t);
};
typedef struct __attribute__((__packed__)) {
uint16_t hardware_type;
uint16_t protocol_type;
uint8_t hardware_len;
uint8_t protocol_len;
uint16_t operation_type;
mac_address_t sender_hardware_address;
ip_address_t sender_protocol_address;
mac_address_t target_hardware_address;
ip_address_t target_protocol_address;
} arp_packet_t;
static_assert(offsetof(arp_packet_t, hardware_type) == 0);
static_assert(offsetof(arp_packet_t, protocol_type) == 2);
static_assert(offsetof(arp_packet_t, hardware_len) == 4);
static_assert(offsetof(arp_packet_t, protocol_len) == 5);
static_assert(offsetof(arp_packet_t, operation_type) == 6);
static_assert(offsetof(arp_packet_t, sender_hardware_address) == 8);
static_assert(offsetof(arp_packet_t, sender_protocol_address) == 14);
static_assert(offsetof(arp_packet_t, target_hardware_address) == 18);
static_assert(offsetof(arp_packet_t, target_protocol_address) == 24);
static_assert(sizeof(arp_packet_t) == 28);
typedef struct {
uint64_t time;
uint64_t cache_timeout;
uint64_t request_timeout;
arp_packet_t *output;
void *send_data;
void (*send)(void *send_data, mac_address_t dest_mac);
ip_address_t self_ip;
mac_address_t self_mac;
arp_translation_table_t table;
arp_pending_request_t *pending_request_free_list;
arp_pending_request_t *pending_request_used_list;
arp_pending_request_t *pending_request_used_tail;
arp_pending_request_t pending_request_pool[ARP_MAX_PENDING_REQUESTS];
} arp_state_t;
typedef enum {
ARP_PROCESS_RESULT_HWARENOTSUPP,
ARP_PROCESS_RESULT_PROTONOTSUPP,
ARP_PROCESS_RESULT_INVALID,
ARP_PROCESS_RESULT_OK,
} arp_process_result_t;
void arp_init(arp_state_t *state, ip_address_t ip, mac_address_t mac, void *send_data, void (*send)(void*, mac_address_t));
void arp_free(arp_state_t *state);
arp_process_result_t arp_process_packet(arp_state_t *state, const void *packet, size_t len);
void arp_resolve_mac(arp_state_t *state, ip_address_t ip, void *userp, void (*callback)(void*, arp_resolution_status_t, mac_address_t));
void arp_seconds_passed(arp_state_t *state, size_t seconds);
void arp_change_output_buffer(arp_state_t *state, void *ptr, size_t max);
+20
View File
@@ -0,0 +1,20 @@
#ifndef MICROTCP_DEFS_H
#define MICROTCP_DEFS_H
#include <stdint.h>
#include <assert.h> // static_assert
typedef struct {
uint8_t data[6];
} mac_address_t;
typedef uint32_t ip_address_t;
static_assert(sizeof(mac_address_t) == 6);
static_assert(sizeof(ip_address_t) == 4);
#define MAC_ZERO (mac_address_t) {.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}
#define MAC_BROADCAST (mac_address_t) {.data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
#endif /* MICROTCP_DEFS_H */
+113
View File
@@ -0,0 +1,113 @@
#include <string.h>
#include <arpa/inet.h>
#include "icmp.h"
#ifdef ICMP_DEBUG
#include <stdio.h>
#define ICMP_DEBUG_LOG(fmt, ...) fprintf(stderr, "ICMP :: " fmt "\n", ## __VA_ARGS__)
#else
#define ICMP_DEBUG_LOG(...)
#endif
typedef enum {
ICMP_TYPE_ECHO_REPLY = 0,
ICMP_TYPE_ECHO_REQUEST = 8,
} icmp_type_t;
typedef struct {
uint8_t type;
uint8_t code;
uint16_t checksum;
uint16_t id_no;
uint16_t seq_no;
uint8_t data[];
} icmp_message_echo_t;
typedef struct {
uint8_t type;
uint8_t code;
uint16_t checksum;
uint8_t data[];
} icmp_message_generic_t;
void icmp_change_output_buffer(icmp_state_t *state, void *ptr, size_t len)
{
state->output_ptr = ptr;
state->output_len = len;
}
void icmp_init(icmp_state_t *state, void *send_data, void (*send)(void*, ip_address_t, size_t))
{
state->output_ptr = NULL;
state->output_len = 0;
state->send_data = send_data;
state->send = send;
}
void icmp_free(icmp_state_t *state)
{
(void) state;
}
static uint16_t calculate_checksum_icmp(const void *src, size_t len)
{
assert((len & 1) == 0);
const uint16_t *src2 = src;
uint32_t sum = 0xffff;
for (size_t i = 0; i < len/2; i++) {
sum += ntohs(src2[i]);
if (sum > 0xffff)
sum -= 0xffff;
}
return htons(~sum);
}
void icmp_process_packet(icmp_state_t *state, ip_address_t ip, const void *src, size_t len)
{
if (len < sizeof(icmp_message_generic_t))
return;
const icmp_message_generic_t *packet = src;
switch (packet->type) {
case ICMP_TYPE_ECHO_REQUEST:
{
if (len < sizeof(icmp_message_echo_t))
return;
const icmp_message_echo_t *echo_request = (icmp_message_echo_t*) packet;
if (calculate_checksum_icmp(echo_request, len)) {
ICMP_DEBUG_LOG("Dropping ICMP message with invalid checksum");
return;
}
if (state->output_ptr == NULL || state->output_len < len) {
ICMP_DEBUG_LOG("Ignoring ECHO REQUEST because the output buffer is too small for an ECHO REPLY (have %ld, need %ld)", state->output_len, len);
return;
}
icmp_message_echo_t *echo_reply = state->output_ptr;
echo_reply->type = ICMP_TYPE_ECHO_REPLY;
echo_reply->code = 0;
echo_reply->checksum = 0;
echo_reply->id_no = echo_request->id_no;
echo_reply->seq_no = echo_request->seq_no;
memcpy(echo_reply->data, echo_request->data, len - sizeof(icmp_message_echo_t));
echo_reply->checksum = calculate_checksum_icmp(echo_reply, len);
ICMP_DEBUG_LOG("Replying to echo request");
state->send(state->send_data, ip, len);
}
break;
default:
// Unsupported ICMP message
break;
}
}
+14
View File
@@ -0,0 +1,14 @@
#include <stddef.h>
#include "defs.h"
typedef struct {
void *output_ptr;
size_t output_len;
void *send_data;
void (*send)(void *send_data, ip_address_t ip, size_t len);
} icmp_state_t;
void icmp_init(icmp_state_t *state, void *send_data, void (*send)(void*, ip_address_t, size_t));
void icmp_free(icmp_state_t *state);
void icmp_process_packet(icmp_state_t *state, ip_address_t ip, const void *src, size_t len);
void icmp_change_output_buffer(icmp_state_t *state, void *ptr, size_t len);
+221
View File
@@ -0,0 +1,221 @@
#include <string.h>
#include <arpa/inet.h> // ntohs()
#include "ip.h"
#ifdef IP_DEBUG
#include <stdio.h>
#define IP_DEBUG_LOG(fmt, ...) do { fprintf(stderr, "IP :: " fmt "\n", ## __VA_ARGS__); } while (0);
#else
#define IP_DEBUG_LOG(...) do { } while (0);
#endif
static uint16_t calculate_checksum_ip(const void *src, size_t len)
{
assert((len & 1) == 0);
const uint16_t *src2 = src;
uint32_t sum = 0xffff;
for (size_t i = 0; i < len/2; i++) {
sum += ntohs(src2[i]);
if (sum > 0xffff)
sum -= 0xffff;
}
return htons(~sum);
}
static ip_plugged_protocol_t *
find_protocol_with_id(ip_state_t *ip_state, uint8_t protocol)
{
for (size_t i = 0; i < ip_state->plugged_protocols_count; i++)
if (protocol == ip_state->plugged_protocols[i].protocol)
return ip_state->plugged_protocols + i;
return NULL;
}
bool ip_plug_protocol(ip_state_t *ip_state, uint8_t protocol,
void *data, void (*process_packet)(void *data, ip_address_t sender, const void *packet, size_t len))
{
if (protocol == IP_PROTOCOL_ICMP)
return false; // Can't override default ICMP module
ip_plugged_protocol_t *p = find_protocol_with_id(ip_state, protocol);
if (!p) {
if (ip_state->plugged_protocols_count == IP_PLUGGED_PROTOCOLS_MAX)
return false;
p = ip_state->plugged_protocols + ip_state->plugged_protocols_count++;
}
p->protocol = protocol;
p->data = data;
p->process_packet = process_packet;
return true;
}
static bool is_packet_one_of_more_fragments(const ip_packet_t *packet)
{
size_t offset = ntohs(packet->fragment_offset) & 0x1FFF;
bool more_fragments = ntohs(packet->fragment_offset) & 0x2000;
return more_fragments || offset;
}
static void send_icmp_packet(void *data, ip_address_t ip, size_t len)
{
ip_state_t *ip_state = data;
// The data was written in the output buffer
ip_packet_t *packet = ip_state->output_ptr; // This changes every iteration
packet->version = 4;
packet->header_length = 5;
packet->type_of_service = 0; // ???
packet->total_length = htons(sizeof(ip_packet_t) + len);
packet->id = ip_state->next_id++;
packet->fragment_offset = 0; // ???
packet->time_to_live = 32; // ???
packet->protocol = IP_PROTOCOL_ICMP;
packet->checksum = 0; // Temporary value
packet->src_ip = ip_state->ip;
packet->dst_ip = ip;
packet->checksum = calculate_checksum_ip((uint16_t*) packet, 4 * packet->header_length);
ip_state->send(ip_state->send_data, ip, sizeof(ip_packet_t) + len);
}
void ip_init(ip_state_t *state,
ip_address_t ip,
void *send_data,
void (*send)(void*, ip_address_t, size_t))
{
state->ip = ip;
state->next_id = 0;
state->send_data = send_data;
state->send = send;
state->output_ptr = NULL;
state->output_max = 0;
state->plugged_protocols_count = 0;
icmp_init(&state->icmp_state, state, send_icmp_packet);
}
void ip_free(ip_state_t *ip_state)
{
icmp_free(&ip_state->icmp_state);
}
void ip_change_output_buffer(ip_state_t *state, void *ptr, size_t max)
{
state->output_ptr = ptr;
state->output_max = max;
icmp_change_output_buffer(&state->icmp_state, (ip_packet_t*) ptr + 1, max - sizeof(ip_packet_t));
}
void ip_seconds_passed(ip_state_t *state, size_t seconds)
{
(void) state;
(void) seconds;
}
int ip_send(ip_state_t *state, ip_protocol_t protocol, ip_address_t dst, bool no_fragm, const void *src, size_t len)
{
size_t managed_payload = 0;
while (managed_payload < len && (managed_payload == 0 || !no_fragm)) {
if (state->output_ptr == NULL) {
// Lower layers of the network stack didn't specify an output
// buffer region. This may be because no memory is available.
// If at least one byte was sent, return gracefully.
// If no byte was sent return an error to the caller.
if (managed_payload > 0)
break;
else
return -1;
}
if (state->output_max <= sizeof(ip_packet_t))
// Output buffer provided by the lower layers of the stack
// isn't big enough for an IP packet containing a single byte.
return -1;
size_t current_payload_limit = state->output_max - sizeof(ip_packet_t);
size_t remaining_payload = len - managed_payload;
size_t considered_payload = MIN(current_payload_limit, remaining_payload);
ip_packet_t *packet = state->output_ptr; // This changes every iteration
packet->version = 4;
packet->header_length = 5;
packet->type_of_service = 0; // ???
packet->total_length = htons(sizeof(ip_packet_t) + considered_payload);
packet->id = state->next_id++;
packet->fragment_offset = 0; // ???
packet->time_to_live = 32; // ???
packet->protocol = protocol;
packet->checksum = 0; // Temporary value
packet->src_ip = state->ip;
packet->dst_ip = dst;
memcpy(packet->payload,
src + managed_payload,
considered_payload);
packet->checksum = calculate_checksum_ip((uint16_t*) packet, 4 * packet->header_length);
// Sending updates the [state->output_ptr] and [state->output_len]
state->send(state->send_data, dst, sizeof(ip_packet_t) + considered_payload);
managed_payload += considered_payload;
}
return managed_payload;
}
void ip_process_packet(ip_state_t *ip_state, const void *packet, size_t len)
{
if (len < sizeof(ip_packet_t))
return;
const ip_packet_t *packet2 = packet;
if (packet2->version != 4 || packet2->header_length < 5) {
IP_DEBUG_LOG("Only supported IPv4 (received %d) with no options", packet2->version);
return;
}
size_t option_count = packet2->header_length - sizeof(ip_packet_t)/4;
if (option_count > 0) {
#warning "TODO: Handle IP options"
return;
}
if (is_packet_one_of_more_fragments(packet2)) {
IP_DEBUG_LOG("Not supporting IP fragmentation");
return;
}
if (calculate_checksum_ip((uint16_t*) packet2, 4 * packet2->header_length)) {
IP_DEBUG_LOG("Dropping IP packet with invalid checksum");
return;
}
if (packet2->dst_ip != ip_state->ip) {
IP_DEBUG_LOG("Packet not for me");
return;
}
ip_plugged_protocol_t *handler = find_protocol_with_id(ip_state, packet2->protocol);
const void *packet3_ptr = packet2+1;
size_t packet3_len = ntohs(packet2->total_length) - sizeof(ip_packet_t);
if (handler)
handler->process_packet(handler->data, packet2->src_ip, packet3_ptr, packet3_len);
else if (packet2->protocol == IP_PROTOCOL_ICMP)
icmp_process_packet(&ip_state->icmp_state, packet2->src_ip, packet3_ptr, packet3_len);
else
IP_DEBUG_LOG("Unsupported protocol %d", packet2->protocol);
}
+67
View File
@@ -0,0 +1,67 @@
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <endian.h>
#include "defs.h"
#include "icmp.h"
#define IP_PLUGGED_PROTOCOLS_MAX 4
typedef struct {
#if __BYTE_ORDER == __LITTLE_ENDIAN
uint8_t header_length: 4;
uint8_t version: 4;
#endif
#if __BYTE_ORDER == __BIG_ENDIAN
uint8_t version: 4;
uint8_t header_length: 4;
#endif
uint8_t type_of_service;
uint16_t total_length;
uint16_t id;
uint16_t fragment_offset;
uint8_t time_to_live;
uint8_t protocol;
uint16_t checksum;
uint32_t src_ip;
uint32_t dst_ip;
char payload[];
} ip_packet_t;
static_assert(sizeof(ip_packet_t) == 20);
typedef enum {
IP_PROTOCOL_ICMP = 1,
IP_PROTOCOL_TCP = 6,
IP_PROTOCOL_UDP = 17,
} ip_protocol_t;
typedef struct {
uint8_t protocol;
void *data;
void (*process_packet)(void*, ip_address_t, const void*, size_t);
} ip_plugged_protocol_t;
typedef struct {
ip_address_t ip;
uint32_t next_id;
void *output_ptr;
size_t output_max;
icmp_state_t icmp_state;
void *send_data;
void (*send)(void*, ip_address_t, size_t);
size_t plugged_protocols_count;
ip_plugged_protocol_t plugged_protocols[IP_PLUGGED_PROTOCOLS_MAX];
} ip_state_t;
void ip_seconds_passed(ip_state_t *state, size_t seconds);
void ip_change_output_buffer(ip_state_t *state, void *ptr, size_t max);
void ip_init(ip_state_t *state, ip_address_t ip, void *send_data, void (*send)(void*, ip_address_t, size_t));
void ip_free(ip_state_t *state);
int ip_send(ip_state_t *state, ip_protocol_t protocol, ip_address_t dst, bool no_fragm, const void *src, size_t len);
void ip_process_packet(ip_state_t *state, const void *packet, size_t len);
bool ip_plug_protocol(ip_state_t *ip_state, uint8_t protocol, void *data, void (*process_packet)(void *data, ip_address_t sender, const void *packet, size_t len));
+758
View File
@@ -0,0 +1,758 @@
#include <time.h> // time()
#include <errno.h>
#include <string.h> // strerror()
#include <stdint.h>
#include <stdlib.h>
#include <netinet/in.h>
#include "ip.h"
#include "arp.h"
#include "tcp.h"
#include <microtcp.h>
#ifdef MICROTCP_BACKGROUND_THREAD
#include <pthread.h>
#endif
#ifdef MICROTCP_DEBUG
#include <stdio.h>
#define MICROTCP_DEBUG_LOG(fmt, ...) do { fprintf(stderr, "MICROTCP :: " fmt "\n", ## __VA_ARGS__); } while (0);
#else
#define MICROTCP_DEBUG_LOG(...) do {} while (0);
#endif
#ifdef MICROTCP_BACKGROUND_THREAD
#define LOCK_WHEN_THREADED(mtcp) do { \
fprintf(stderr, "--- %s before lock\n", __func__); \
fflush(stderr); \
pthread_mutex_lock(&(mtcp)->lock); \
fprintf(stderr, "--- %s after lock\n", __func__); \
fflush(stderr); \
} while (0);
#define UNLOCK_WHEN_THREADED(mtcp) do { \
fprintf(stderr, "--- %s before unlock\n", __func__); \
fflush(stderr); \
pthread_mutex_unlock(&(mtcp)->lock); \
fprintf(stderr, "--- %s after unlock\n", __func__); \
fflush(stderr); \
} while (0);
//#define UNLOCK_WHEN_THREADED(mtcp) do { pthread_mutex_unlock(&(mtcp)->lock); } while (0);
#else
#define LOCK_WHEN_THREADED(mtcp) do { (void) (mtcp); } while (0);
#define UNLOCK_WHEN_THREADED(mtcp) do { (void) (mtcp); } while (0);
#endif
typedef struct buffer_t buffer_t;
struct buffer_t {
microtcp_t *mtcp;
buffer_t *prev;
buffer_t *next;
size_t used;
char data[1518];
};
typedef enum {
SOCKET_LISTENER,
SOCKET_CONNECTION,
} socket_type_t;
struct microtcp_socket_t {
microtcp_t *mtcp;
microtcp_socket_t *prev;
microtcp_socket_t *next;
socket_type_t type;
union {
tcp_listener_t *listener;
tcp_connection_t *connection;
};
#ifdef MICROTCP_BACKGROUND_THREAD
pthread_cond_t something_to_accept;
#endif
};
struct microtcp_t {
time_t last_update_time;
#ifdef MICROTCP_BACKGROUND_THREAD
bool thread_should_stop;
pthread_t thread_id;
pthread_mutex_t lock;
#endif
microtcp_callbacks_t callbacks;
ip_address_t ip;
mac_address_t mac;
ip_state_t ip_state;
arp_state_t arp_state;
tcp_state_t tcp_state;
buffer_t *used_buffer;
buffer_t *wait_buffer_list;
buffer_t *free_buffer_list;
buffer_t buffer_pool[MICROTCP_MAX_BUFFERS];
microtcp_socket_t *used_socket_list;
microtcp_socket_t *free_socket_list;
microtcp_socket_t socket_pool[MICROTCP_MAX_SOCKETS];
};
const char *microtcp_strerror(microtcp_errcode_t errcode)
{
switch (errcode) {
case MICROTCP_ERRCODE_NONE: return "No error occurred";
case MICROTCP_ERRCODE_SOCKETLIMIT: return "Can't create a socket because the socket limit per microtcp instance was reached";
case MICROTCP_ERRCODE_TCPERROR: return "An error occurred at the TCP layer";
case MICROTCP_ERRCODE_BADCONDVAR: return "Condition variable error";
case MICROTCP_ERRCODE_NOTLISTENER: return "Invalid operation on a non-listener socket";
case MICROTCP_ERRCODE_CANTBLOCK: return "Can't execute a blocking call for this function";
case MICROTCP_ERRCODE_NOTHINGTOACCEPT: return "Accept queue is empty";
case MICROTCP_ERRCODE_NOTCONNECTION: return "Invalid operation on a non-connection socket";
}
return "???";
}
typedef enum {
ETHERNET_PROTOCOL_ARP = 0x0806,
ETHERNET_PROTOCOL_IP = 0x0800,
} ethernet_protocol_t;
typedef struct {
mac_address_t dst;
mac_address_t src;
uint16_t proto;
} __attribute__((packed)) ethernet_frame_t;
static_assert(sizeof(ethernet_frame_t) == 14);
#ifdef MICROTCP_DEBUG
static bool is_valid_buffer_pointer(microtcp_t *mtcp, buffer_t *buffer)
{
for (size_t i = 0; i < MICROTCP_MAX_BUFFERS; i++)
if (buffer == mtcp->buffer_pool + i)
return true;
return false;
}
#endif
static void send_arp_packet(void *data, mac_address_t dst)
{
microtcp_t *mtcp = data;
buffer_t *buffer = mtcp->used_buffer;
#ifdef MICROTCP_DEBUG
assert(is_valid_buffer_pointer(mtcp, buffer));
#endif
buffer->used = sizeof(ethernet_frame_t) + sizeof(arp_packet_t);
ethernet_frame_t *frame = (ethernet_frame_t*) buffer->data;
frame->dst = dst;
frame->src = mtcp->mac;
frame->proto = htons(ETHERNET_PROTOCOL_ARP);
// TODO: What about the CRC?
#warning "TODO: Calculate Ethernet CRC"
int n = mtcp->callbacks.send(mtcp->callbacks.data, buffer->data, buffer->used);
if (n < 0)
MICROTCP_DEBUG_LOG("Couldn't send (%s)", strerror(errno));
// Now reset the used buffer
mtcp->used_buffer->used = 0;
}
static int send_tcp_segment(void *data, ip_address_t dst,
const void *str, size_t len)
{
microtcp_t *mtcp = data;
return ip_send(&mtcp->ip_state, IP_PROTOCOL_TCP, dst, true, str, len);
}
static void move_wait_buffer_to_free_list(buffer_t *buffer)
{
microtcp_t *mtcp = buffer->mtcp;
#ifdef MICROTCP_DEBUG
assert(is_valid_buffer_pointer(mtcp, buffer));
assert(buffer->prev == NULL || is_valid_buffer_pointer(mtcp, buffer->prev));
assert(buffer->next == NULL || is_valid_buffer_pointer(mtcp, buffer->next));
#endif
if (buffer->prev)
buffer->prev->next = buffer->next;
else
mtcp->wait_buffer_list = buffer->next;
if (buffer->next)
buffer->next->prev = buffer->prev;
#ifdef MICROTCP_DEBUG
assert(mtcp->free_buffer_list == NULL || is_valid_buffer_pointer(mtcp, mtcp->free_buffer_list));
assert(mtcp->free_buffer_list == NULL || mtcp->free_buffer_list->prev == NULL);
assert(mtcp->free_buffer_list == NULL || mtcp->free_buffer_list->next == NULL || is_valid_buffer_pointer(mtcp, mtcp->free_buffer_list->next));
#endif
buffer->prev = NULL;
buffer->next = mtcp->free_buffer_list;
mtcp->free_buffer_list = buffer;
}
static void mac_resolved(void *data, arp_resolution_status_t status, mac_address_t mac)
{
buffer_t *buffer = data;
microtcp_t *mtcp = buffer->mtcp;
#ifdef MICROTCP_DEBUG
assert(is_valid_buffer_pointer(mtcp, buffer));
#endif
switch (status) {
case ARP_RESOLUTION_OK:
{
ethernet_frame_t *frame = (ethernet_frame_t*) buffer->data;
frame->dst = mac;
int n = mtcp->callbacks.send(mtcp->callbacks.data, buffer->data, buffer->used);
if (n < 0)
MICROTCP_DEBUG_LOG("Couldn't send (%s)", strerror(errno));
}
break;
case ARP_RESOLUTION_FAILED:
MICROTCP_DEBUG_LOG("MAC resolution failed");
break;
case ARP_RESOLUTION_TIMEOUT:
MICROTCP_DEBUG_LOG("MAC resolution timeout");
break;
}
move_wait_buffer_to_free_list(buffer);
}
static void move_used_buffer_to_wait_list(microtcp_t *mtcp)
{
buffer_t *buffer = mtcp->used_buffer;
mtcp->used_buffer = NULL;
#ifdef MICROTCP_DEBUG
assert(is_valid_buffer_pointer(mtcp, buffer));
#endif
buffer->next = mtcp->wait_buffer_list;
if (mtcp->wait_buffer_list)
mtcp->wait_buffer_list->prev = buffer;
mtcp->wait_buffer_list = buffer;
ip_change_output_buffer(&mtcp->ip_state, NULL, 0);
arp_change_output_buffer(&mtcp->arp_state, NULL, 0);
}
static void use_a_buffer(microtcp_t *mtcp)
{
#ifdef MIRCOTCP_DEBUG
assert(mtcp->free_buffer_list == NULL || is_valid_buffer_pointer(mtcp, mtcp->free_buffer_list));
assert(mtcp->free_buffer_list == NULL || mtcp->free_buffer_list->prev == NULL);
assert(mtcp->free_buffer_list == NULL || mtcp->free_buffer_list->next == NULL || is_valid_buffer_pointer(mtcp, mtcp->free_buffer_list->next));
#endif
// At this moment the network stack has no allocated
// output buffer but wants to allocate one (by calling
// this function).
// It's assumed there is no output buffer, hence:
//
assert(mtcp->used_buffer == NULL);
//
// To allocate a buffer, we need to pop it from the
// buffer free list, which is a singly-linked list of
// unused buffers. Once it's been popped off the list,
// we need to tell the upper layers of the stack that
// this is the new output buffer.
//
// If the free list is empty, no buffer is allocated.
//
if (!mtcp->free_buffer_list)
return; // No free buffers available in the free list.
//
// Pop a buffer from the free list
buffer_t *buffer = mtcp->free_buffer_list;
mtcp->free_buffer_list = buffer->next;
//
// Initialize the buffer
buffer->mtcp = mtcp;
buffer->used = 0;
buffer->prev = NULL;
buffer->next = NULL;
//
// Set it as the output buffer
mtcp->used_buffer = buffer;
//
// Now tell the upper layers where they'll output
// the data, but reserve the first bytes of the buffer
// for the ethernet header.
//
void *output_ptr = buffer->data + sizeof(ethernet_frame_t);
size_t output_max = sizeof(buffer->data) - sizeof(ethernet_frame_t);
ip_change_output_buffer(&mtcp->ip_state, output_ptr, output_max);
arp_change_output_buffer(&mtcp->arp_state, output_ptr, output_max);
}
static void send_ip_packet(void *data, ip_address_t ip, size_t len)
{
microtcp_t *mtcp = data;
buffer_t *buffer = mtcp->used_buffer;
if (buffer == NULL)
// The IP layer wants to send something, but no output
// buffer was associated to it. This function should not
// have been called by the IP layer without a buffer.
return;
buffer->used = sizeof(ethernet_frame_t) + len;
move_used_buffer_to_wait_list(mtcp);
use_a_buffer(mtcp);
ethernet_frame_t *frame = (ethernet_frame_t*) buffer->data;
frame->src = mtcp->mac;
frame->dst = MAC_ZERO; // We need to determine it
frame->proto = htons(ETHERNET_PROTOCOL_IP);
arp_resolve_mac(&mtcp->arp_state, ip, buffer, mac_resolved);
}
static void
tcp_process_segment_wrapper(void *data, ip_address_t ip, const void *packet, size_t len)
{
if (len >= sizeof(tcp_segment_t))
tcp_process_segment((tcp_state_t*) data, ip, (tcp_segment_t*) packet, len);
}
static void
process_packet(microtcp_t *mtcp, const void *packet, size_t len)
{
if (len < sizeof(ethernet_frame_t))
return;
const ethernet_frame_t *frame = packet;
switch (ntohs(frame->proto)) {
case ETHERNET_PROTOCOL_ARP:
arp_process_packet(&mtcp->arp_state, frame+1, len - sizeof(ethernet_frame_t));
break;
case ETHERNET_PROTOCOL_IP:
ip_process_packet(&mtcp->ip_state, frame+1, len - sizeof(ethernet_frame_t));
break;
default:
// Unsupported ethertype
MICROTCP_DEBUG_LOG("Ignoring packet with ethertype %4x", frame->proto);
break;
}
}
void microtcp_process_packet(microtcp_t *mtcp, const void *packet, size_t len)
{
LOCK_WHEN_THREADED(mtcp);
process_packet(mtcp, packet, len);
UNLOCK_WHEN_THREADED(mtcp);
}
void microtcp_step(microtcp_t *mtcp)
{
char packet[UINT16_MAX];
// The call to [recv] (which is assumed to be blocking)
// needs to be out of the critical section to give other
// threads the ability to progress in the mean time.
int size = mtcp->callbacks.recv(mtcp->callbacks.data, packet, sizeof(packet));
if (size < 0)
return;
LOCK_WHEN_THREADED(mtcp);
{
process_packet(mtcp, packet, size);
time_t current_time = time(NULL);
int secs = (float) (current_time - mtcp->last_update_time);
if (secs > 0) {
ip_seconds_passed(&mtcp->ip_state, secs);
arp_seconds_passed(&mtcp->arp_state, secs);
tcp_seconds_passed(&mtcp->tcp_state, secs);
mtcp->last_update_time = current_time;
}
}
unlock_and_exit:
UNLOCK_WHEN_THREADED(mtcp);
}
#ifdef MICROTCP_BACKGROUND_THREAD
static void *loop(void *data)
{
microtcp_t *mtcp = data;
while (!mtcp->thread_should_stop)
microtcp_step(mtcp);
return NULL;
}
#endif
microtcp_t *microtcp_create_using_callbacks(microtcp_ip_t ip, microtcp_mac_t mac,
microtcp_callbacks_t callbacks)
{
microtcp_t *mtcp = malloc(sizeof(microtcp_t));
if (mtcp == NULL)
return NULL;
mac_address_t mac2;
static_assert(sizeof(mac2) == sizeof(mac));
memcpy(&mac2, &mac, sizeof(mac));
mtcp->ip = ip;
mtcp->mac = mac2;
mtcp->callbacks = callbacks;
mtcp->last_update_time = time(NULL);
mtcp->used_buffer = NULL;
mtcp->wait_buffer_list = NULL;
mtcp->free_buffer_list = mtcp->buffer_pool;
for (size_t i = 0; i < MICROTCP_MAX_BUFFERS-1; i++) {
mtcp->buffer_pool[i].mtcp = NULL;
mtcp->buffer_pool[i].prev = NULL;
mtcp->buffer_pool[i].next = mtcp->buffer_pool + i+1;
}
mtcp->buffer_pool[MICROTCP_MAX_BUFFERS-1].mtcp = NULL;
mtcp->buffer_pool[MICROTCP_MAX_BUFFERS-1].prev = NULL;
mtcp->buffer_pool[MICROTCP_MAX_BUFFERS-1].next = NULL;
mtcp->used_socket_list = NULL;
mtcp->free_socket_list = mtcp->socket_pool;
for (size_t i = 0; i < MICROTCP_MAX_SOCKETS-1; i++) {
mtcp->socket_pool[i].mtcp = NULL;
mtcp->socket_pool[i].prev = NULL;
mtcp->socket_pool[i].next = mtcp->socket_pool + i + 1;
}
mtcp->socket_pool[MICROTCP_MAX_SOCKETS-1].mtcp = NULL;
mtcp->socket_pool[MICROTCP_MAX_SOCKETS-1].prev = NULL;
mtcp->socket_pool[MICROTCP_MAX_SOCKETS-1].next = NULL;
ip_init(&mtcp->ip_state, ip, mtcp, send_ip_packet);
if (!ip_plug_protocol(&mtcp->ip_state, IP_PROTOCOL_TCP, &mtcp->tcp_state, tcp_process_segment_wrapper)) {
free(mtcp);
return NULL;
}
arp_init(&mtcp->arp_state, ip, mac2, mtcp, send_arp_packet);
tcp_init(&mtcp->tcp_state, (tcp_callbacks_t) {
.data = mtcp,
.send = send_tcp_segment,
});
use_a_buffer(mtcp);
#ifdef MICROTCP_BACKGROUND_THREAD
{
if (pthread_mutex_init(&mtcp->lock, NULL)) {
ip_free(&mtcp->ip_state);
arp_free(&mtcp->arp_state);
tcp_free(&mtcp->tcp_state);
free(mtcp);
return NULL;
}
mtcp->thread_should_stop = false;
if (pthread_create(&mtcp->thread_id, NULL, loop, mtcp)) {
ip_free(&mtcp->ip_state);
arp_free(&mtcp->arp_state);
tcp_free(&mtcp->tcp_state);
free(mtcp);
return NULL;
}
}
#endif
MICROTCP_DEBUG_LOG("Instanciated ("
"debug="
#ifdef MICROTCP_DEBUG
"yes"
#else
"no"
#endif
", thread="
#ifdef MICROTCP_BACKGROUND_THREAD
"yes"
#else
"no"
#endif
")");
return mtcp;
}
void microtcp_destroy(microtcp_t *mtcp)
{
#ifdef MICROTCP_BACKGROUND_THREAD
MICROTCP_DEBUG_LOG("Stopping thread");
mtcp->thread_should_stop = true;
pthread_join(mtcp->thread_id, NULL);
pthread_mutex_destroy(&mtcp->lock);
MICROTCP_DEBUG_LOG("Thread stopped");
#endif
ip_free(&mtcp->ip_state);
arp_free(&mtcp->arp_state);
tcp_free(&mtcp->tcp_state);
if (mtcp->callbacks.free)
mtcp->callbacks.free(mtcp->callbacks.data);
}
static microtcp_socket_t*
pop_socket_struct_from_free_list(microtcp_t *mtcp)
{
microtcp_socket_t *socket = mtcp->free_socket_list;
mtcp->free_socket_list = socket->next;
return socket;
}
static bool
have_unused_socket_structs(microtcp_t *mtcp)
{
return mtcp->free_socket_list != NULL;
}
static void
push_unlinked_socket_into_used_list(microtcp_socket_t *socket)
{
microtcp_t *mtcp = socket->mtcp;
socket->next = mtcp->used_socket_list;
if (mtcp->used_socket_list)
mtcp->used_socket_list->prev = socket;
mtcp->used_socket_list = socket;
}
static void
unlink_socket_from_used_socket_list(microtcp_socket_t *socket)
{
microtcp_t *mtcp = socket->mtcp;
if (socket->prev)
socket->prev->next = socket->next;
else
mtcp->used_socket_list = socket->next;
if (socket->next)
socket->next->prev = socket->prev;
socket->prev = NULL;
socket->next = NULL;
}
static void
push_unlinked_socket_into_free_list(microtcp_t *mtcp, microtcp_socket_t *socket)
{
socket->prev = NULL;
socket->next = mtcp->free_socket_list;
mtcp->free_socket_list = socket;
}
static void ready_to_accept(void *data)
{
#ifdef MICROTCP_BACKGROUND_THREAD
microtcp_socket_t *socket = data;
pthread_cond_signal(&socket->something_to_accept);
#else
(void) data;
#endif
}
microtcp_socket_t *microtcp_open(microtcp_t *mtcp, uint16_t port,
microtcp_errcode_t *errcode)
{
microtcp_errcode_t errcode2 = MICROTCP_ERRCODE_NONE;
microtcp_socket_t *socket = NULL;
LOCK_WHEN_THREADED(mtcp);
{
socket = pop_socket_struct_from_free_list(mtcp);
if (!socket) {
errcode2 = MICROTCP_ERRCODE_SOCKETLIMIT;
goto unlock_and_exit; // Socket limit reached
}
tcp_listener_t *listener = tcp_listener_create(&mtcp->tcp_state, port, socket, ready_to_accept);
if (listener == NULL) {
#warning "This error code should be more specific, but the TCP module isn't stable yet"
errcode2 = MICROTCP_ERRCODE_TCPERROR;
push_unlinked_socket_into_free_list(mtcp, socket);
goto unlock_and_exit;
}
socket->mtcp = mtcp;
socket->prev = NULL;
socket->next = NULL;
socket->type = SOCKET_LISTENER;
socket->listener = listener;
#ifdef MICROTCP_BACKGROUND_THREAD
if (pthread_cond_init(&socket->something_to_accept, NULL)) {
errcode2 = MICROTCP_ERRCODE_BADCONDVAR;
push_unlinked_socket_into_free_list(mtcp, socket);
tcp_listener_destroy(listener);
goto unlock_and_exit;
}
#endif
push_unlinked_socket_into_used_list(socket);
}
unlock_and_exit:
UNLOCK_WHEN_THREADED(mtcp);
if (errcode)
*errcode = errcode2;
return socket;
}
void microtcp_close(microtcp_socket_t *socket)
{
if (!socket)
return;
microtcp_t *mtcp = socket->mtcp;
LOCK_WHEN_THREADED(mtcp);
{
switch (socket->type) {
case SOCKET_LISTENER:
#ifdef MICROTCP_BACKGROUND_THREAD
pthread_cond_destroy(&socket->something_to_accept);
#endif
tcp_listener_destroy(socket->listener);
break;
case SOCKET_CONNECTION:
tcp_connection_destroy(socket->connection);
break;
}
unlink_socket_from_used_socket_list(socket);
push_unlinked_socket_into_free_list(mtcp, socket);
}
UNLOCK_WHEN_THREADED(mtcp);
}
microtcp_socket_t *microtcp_accept(microtcp_socket_t *socket,
bool no_block,
microtcp_errcode_t *errcode)
{
microtcp_errcode_t errcode2 = MICROTCP_ERRCODE_NONE;
microtcp_t *mtcp = socket->mtcp;
microtcp_socket_t *socket2 = NULL;
LOCK_WHEN_THREADED(mtcp);
{
if (socket->type != SOCKET_LISTENER) {
errcode2 = MICROTCP_ERRCODE_NOTLISTENER;
goto unlock_and_exit; // Can't accept from a non-listening socket
}
if (!have_unused_socket_structs(mtcp)) {
errcode2 = MICROTCP_ERRCODE_SOCKETLIMIT;
goto unlock_and_exit; // Socket limit reached
}
tcp_connection_t *connection = tcp_listener_accept(socket->listener);
if (!connection) {
#ifdef MICROTCP_BACKGROUND_THREAD
if (no_block) {
errcode2 = MICROTCP_ERRCODE_NOTHINGTOACCEPT;
goto unlock_and_exit;
}
do {
pthread_cond_wait(&socket->something_to_accept, &mtcp->lock);
connection = tcp_listener_accept(socket->listener);
} while (!connection);
#else
if (!no_block)
errcode2 = MICROTCP_ERRCODE_CANTBLOCK;
else
errcode2 = MICROTCP_ERRCODE_NOTHINGTOACCEPT;
goto unlock_and_exit;
#endif
}
socket2 = pop_socket_struct_from_free_list(mtcp);
assert(socket2); // Because we checked at the start
socket2->mtcp = mtcp;
socket2->prev = NULL;
socket2->next = NULL;
socket2->type = SOCKET_CONNECTION;
socket2->connection = connection;
push_unlinked_socket_into_used_list(socket2);
}
unlock_and_exit:
UNLOCK_WHEN_THREADED(mtcp);
if (errcode)
*errcode = errcode2;
return socket2;
}
size_t microtcp_recv(microtcp_socket_t *socket,
void *dst, size_t len,
microtcp_errcode_t *errcode)
{
if (!socket || socket->type != SOCKET_CONNECTION) {
if (errcode)
*errcode = MICROTCP_ERRCODE_NOTCONNECTION;
return 0;
}
microtcp_t *mtcp = socket->mtcp;
LOCK_WHEN_THREADED(mtcp);
size_t num = tcp_connection_recv(socket->connection, dst, len);
UNLOCK_WHEN_THREADED(mtcp);
if (errcode)
*errcode = MICROTCP_ERRCODE_NONE;
return num;
}
size_t microtcp_send(microtcp_socket_t *socket,
const void *src, size_t len,
microtcp_errcode_t *errcode)
{
if (!socket || socket->type != SOCKET_CONNECTION) {
if (errcode)
*errcode = MICROTCP_ERRCODE_NOTCONNECTION;
return 0;
}
microtcp_t *mtcp = socket->mtcp;
LOCK_WHEN_THREADED(mtcp);
size_t num = tcp_connection_send(socket->connection, src, len);
UNLOCK_WHEN_THREADED(mtcp);
if (errcode)
*errcode = MICROTCP_ERRCODE_NONE;
return num;
}
+181
View File
@@ -0,0 +1,181 @@
#ifdef MICROTCP_LINUX
#include <poll.h>
#include <errno.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <net/if_arp.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "defs.h"
#include "microtcp.h"
static int systemf(const char *fmt, ...)
{
char buffer[256];
va_list va;
va_start(va, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, va);
va_end(va);
fprintf(stderr, "INFO: Running [%s]\n", buffer);
return system(buffer);
}
static int create_tun_device(const char *dev, char dev2[IFNAMSIZ])
{
int fd = open("/dev/net/tun", O_RDWR);
if (fd < 0)
return -1;
struct ifreq ifr;
memset(&ifr, 0, sizeof(struct ifreq));
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
if (dev && *dev)
strncpy(ifr.ifr_name, dev, IFNAMSIZ);
int err = ioctl(fd, TUNSETIFF, &ifr);
if (err) {
close(fd);
return -1;
}
strncpy(dev2, ifr.ifr_name, IFNAMSIZ);
return fd;
}
static int send_callback(void *data, const void *src, size_t len)
{
int tap_fd = (int) data;
return write(tap_fd, src, len);
}
static int recv_callback(void *data, void *dst, size_t len)
{
int tap_fd = (int) data;
int timeout = 1000;
struct pollfd pfd = {.fd=tap_fd, .events=POLLIN};
int status = poll(&pfd, 1, timeout);
if (status < 1) {
return status;
}
int num = read(tap_fd, dst, len);
return num;
}
static void free_callback(void *data)
{
int tap_fd = (int) data;
close(tap_fd);
}
/*
static bool get_ip_address(const char *dev, microtcp_ip_t *ip)
{
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0)
return false;
struct ifreq ifr;
memset(&ifr, 0, sizeof(struct ifreq));
ifr.ifr_addr.sa_family = AF_INET;
strncpy(ifr.ifr_name, dev, IFNAMSIZ-1);
ioctl(fd, SIOCGIFADDR, &ifr);
memcpy(ip, &((struct sockaddr_in*) &ifr.ifr_addr)->sin_addr, sizeof(microtcp_ip_t));
close(fd);
return true;
}
*/
static bool get_mac_address(const char *dev, microtcp_mac_t *mac)
{
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0)
return false;
struct ifreq ifr;
memset(&ifr, 0, sizeof(struct ifreq));
strncpy(ifr.ifr_name, dev, IFNAMSIZ-1);
ioctl(fd, SIOCGIFHWADDR, &ifr);
if (ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER)
return false;
memcpy(mac, ifr.ifr_hwaddr.sa_data, sizeof(microtcp_mac_t));
close(fd);
return true;
}
microtcp_t *microtcp_create()
{
char dev[IFNAMSIZ];
int tap_fd = create_tun_device("tap0", dev);
if (tap_fd < 0) {
fprintf(stderr, "ERROR: Failed creating TAP device (%s)\n", strerror(errno));
return NULL;
}
fprintf(stderr, "INFO: Using TAP device %s\n", dev);
const char *tap_addr = "10.0.0.5";
const char *tap_route = "10.0.0.0/24";
systemf("ip link set dev %s up", dev);
systemf("ip route add dev %s %s", dev, tap_route);
systemf("ip address add dev %s local %s", dev, tap_addr);
systemf("sudo sysctl -w net.ipv4.ip_forward=1");
microtcp_mac_t mac;
if (!get_mac_address(dev, &mac)) {
fprintf(stderr, "FATAL: Failed to query NIC for IP or MAC (%s)\n", strerror(errno));
close(tap_fd);
return NULL;
}
mac.data[5]++;
microtcp_ip_t ip;
inet_pton(AF_INET, "10.0.0.4", &ip);
struct in_addr ip2;
ip2.s_addr = ip;
fprintf(stderr, "INFO: Using IP %s\n", inet_ntoa(ip2));
fprintf(stderr, "INFO: Using MAC %x:%x:%x:%x:%x:%x\n", mac.data[0], mac.data[1], mac.data[2], mac.data[3], mac.data[4], mac.data[5]);
microtcp_callbacks_t callbacks = {
.data = (int) tap_fd,
.send = send_callback,
.recv = recv_callback,
.free = free_callback,
};
microtcp_t *mtcp = microtcp_create_using_callbacks(ip, mac, callbacks);
if (!mtcp) {
close(tap_fd);
return NULL;
}
return mtcp;
}
#endif /* MICROTCP_LINUX */
+468
View File
@@ -0,0 +1,468 @@
#include <string.h>
#include <arpa/inet.h>
#include "tcp.h"
#ifdef TCP_DEBUG
#include <stdio.h>
#define TCP_DEBUG_LOG(fmt, ...) fprintf(stderr, "TCP :: " fmt "\n", ## __VA_ARGS__)
#else
#define TCP_DEBUG_LOG(...)
#endif
static int tcp_send(tcp_state_t *tcp_state, ip_address_t ip,
const void *src, size_t len)
{
return tcp_state->callbacks.send(tcp_state->callbacks.data, ip, src, len);
}
void tcp_init(tcp_state_t *tcp_state, tcp_callbacks_t callbacks)
{
tcp_state->callbacks = callbacks;
for (size_t i = 0; i < TCP_MAX_SOCKETS-1; i++)
tcp_state->connection_pool[i].next = tcp_state->connection_pool + i+1;
tcp_state->connection_pool[TCP_MAX_SOCKETS-1].next = NULL;
tcp_state->free_connection_list = tcp_state->connection_pool;
tcp_state->used_connection_list = NULL;
for (size_t i = 0; i < TCP_MAX_LISTENERS-1; i++)
tcp_state->listener_pool[i].next = tcp_state->listener_pool + i+1;
tcp_state->listener_pool[TCP_MAX_LISTENERS-1].next = NULL;
tcp_state->free_listener_list = tcp_state->listener_pool;
tcp_state->used_listener_list = NULL;
}
void tcp_free(tcp_state_t *tcp_state)
{
// Destroy all listening connections
while (tcp_state->used_listener_list != NULL)
tcp_listener_destroy(tcp_state->used_listener_list);
}
void tcp_seconds_passed(tcp_state_t *state, size_t seconds)
{
(void) state;
(void) seconds;
}
static tcp_connection_t*
connection_create_waiting_for_ack(tcp_listener_t *listener,
uint32_t seq_no, uint32_t ack_no,
ip_address_t peer_ip, uint16_t peer_port)
{
tcp_state_t *state = listener->state;
// Pop a connection structure from the free list
if (state->free_connection_list == NULL)
// ERROR: Reached connection limit
return NULL;
tcp_connection_t *connection = state->free_connection_list;
state->free_connection_list = connection->next;
// Initialize connection structure
connection->listener = listener;
connection->seq_no = seq_no;
connection->ack_no = ack_no;
connection->peer_port = peer_port;
connection->peer_ip = peer_ip;
connection->in_used = 0;
connection->out_used = 0;
connection->prev = NULL;
connection->next = NULL;
// Append the connection to the list of connections
// waiting for the ACK message
if (listener->connections_waiting_for_ack)
listener->connections_waiting_for_ack->prev = connection;
connection->next = listener->connections_waiting_for_ack;
listener->connections_waiting_for_ack = connection;
return connection;
}
static tcp_listener_t*
find_listener_with_port(tcp_state_t *state, uint16_t port)
{
TCP_DEBUG_LOG("Looking for listener with port %d", port);
tcp_listener_t *cursor = state->used_listener_list;
while (cursor) {
TCP_DEBUG_LOG("port=%d, seeking=%d", cursor->port, port);
if (cursor->port == port)
return cursor;
cursor = cursor->next;
}
return NULL;
}
#include <stdio.h>
typedef enum {
SOCKET_WAIT,
SOCKET_IDLE,
SOCKET_NONE,
} connection_state_t;
static tcp_connection_t *find_connection(tcp_connection_t *list, ip_address_t peer_ip, uint16_t peer_port)
{
tcp_connection_t *cursor = list;
while (cursor) {
if (cursor->peer_ip == peer_ip && cursor->peer_port == peer_port)
return cursor;
cursor = cursor->next;
}
return NULL;
}
static connection_state_t find_connection_associated_to(tcp_listener_t *listener, ip_address_t peer_ip, uint16_t peer_port, tcp_connection_t **connection)
{
tcp_connection_t *connection2 = find_connection(listener->connections, peer_ip, peer_port);
if (connection2) {
*connection = connection2;
return SOCKET_IDLE;
}
connection2 = find_connection(listener->connections_waiting_for_accept_head, peer_ip, peer_port);
if (connection2) {
*connection = connection2;
return SOCKET_IDLE;
}
connection2 = find_connection(listener->connections_waiting_for_ack, peer_ip, peer_port);
if (connection2) {
*connection = connection2;
return SOCKET_WAIT;
}
*connection = NULL;
return SOCKET_NONE;
}
static uint32_t choose_ack()
{
return 0;
}
void tcp_process_segment(tcp_state_t *state, ip_address_t sender,
tcp_segment_t *segment, size_t len)
{
(void) state;
(void) sender;
(void) segment;
(void) len;
TCP_DEBUG_LOG("Received TCP segment");
if (len < sizeof(tcp_segment_t))
return;
uint16_t reordered_dst_port = ntohs(segment->dst_port);
uint16_t reordered_src_port = ntohs(segment->src_port);
tcp_listener_t *listener = find_listener_with_port(state, reordered_dst_port);
if (listener == NULL) {
// No connection is listening on this port. Silently drop the packet.
TCP_DEBUG_LOG("Segment sent to port %d, which is closed", reordered_dst_port);
return;
}
tcp_connection_t *connection;
connection_state_t connection_state = find_connection_associated_to(listener, sender, reordered_src_port, &connection);
if ((segment->flags & TCP_FLAG_SYN) && !(segment->flags & TCP_FLAG_ACK)) {
/* Connection request */
if (connection_state != SOCKET_NONE) {
// Peer wants to connect, but a connection was already created..
// What to do?
#warning "TODO: Handle case where an existing connection recieved the SYN message"
return;
}
// Temporary
uint32_t seq_no = ntohs(segment->seq_no);
uint32_t ack_no = choose_ack();
tcp_connection_t *connection = connection_create_waiting_for_ack(listener, seq_no, ack_no, sender, reordered_dst_port);
if (connection == NULL) {
// ERROR: Socket limit reached. Drop the connection silently
#warning "TODO: Handle connection limit reached (RST?)"
return;
}
tcp_segment_t segment2 = {
.src_port = segment->dst_port,
.dst_port = segment->src_port,
.flags = TCP_FLAG_SYN | TCP_FLAG_ACK, // No need for fixing endianess, it's just one byte!
.seq_no = htons(ack_no),
.ack_no = htons(seq_no),
.offset = 5,
.unused = 0,
.window = htons(TCP_INPUT_BUFFER_SIZE - connection->in_used),
.checksum = 0,
.urgent_pointer = 0,
};
#warning "TODO: Calculare checksum"
tcp_send(state, sender, &segment2, sizeof(segment2));
#warning "TODO: Handle TCP connection creation"
} else if ((segment->flags & TCP_FLAG_SYN) && (segment->flags & TCP_FLAG_ACK)) {
// Drop the packet. We only do servers for now!
#warning "TODO: Handle TCP second message of three way handshake"
} else if (!(segment->flags & TCP_FLAG_SYN) && (segment->flags & TCP_FLAG_ACK)) {
if (connection_state != SOCKET_WAIT) {
// Either there is no connection or the connection wasn't
// waiting for an ACK segment. What to do?
#warning "TODO: Handle case where an existing connection recieved the SYN message"
return;
}
// Move connection from the waiting-for-ack list
// to the waiting-for-accept queue.
// Unlink it from the current list
{
if (connection->prev)
connection->prev->next = connection->next;
else
listener->connections_waiting_for_ack = connection->next;
if (connection->next)
connection->next->prev = connection->prev;
}
// Push it to the new one
{
connection->prev = NULL;
connection->next = listener->connections_waiting_for_accept_head;
if (listener->connections_waiting_for_accept_head)
// Accept queue isn't empty
listener->connections_waiting_for_accept_head->prev = connection;
else
// Accept queue is empty
listener->connections_waiting_for_accept_tail = connection;
listener->connections_waiting_for_accept_head = connection;
}
if (listener->callback)
listener->callback(listener->data);
// TODO: What about the payload?
#warning "TODO: Handle payload of TCP ACK message"
} else {
if (connection_state != SOCKET_IDLE) {
// Either there is no connection associated to this peer
// at this port, or the connection is waiting for an ACK.
// What to do?
#warning "TODO: Handle case where unexpected TCP data message is received"
return;
}
char *payload = segment->payload;
size_t payload_arrived = len - sizeof(tcp_segment_t);
size_t payload_capacity = TCP_INPUT_BUFFER_SIZE - connection->in_used;
size_t payload_considered = MIN(payload_arrived, payload_capacity);
memcpy(connection->in_buffer + connection->in_used, payload, payload_considered);
connection->in_used += payload_considered;
connection->ack_no += payload_considered; // Is this right?
}
}
tcp_listener_t*
tcp_listener_create(tcp_state_t *state, uint16_t port, void *data, void (*callback)(void*))
{
if (find_listener_with_port(state, port)) {
// ERROR: A connection is already listening on this port
TCP_DEBUG_LOG("Faile to create listener on port %d because there already exists one", port);
return NULL;
}
// Pop a listener connection structure from the free list
if (state->free_listener_list == NULL) {
// ERROR: Reached listener connection limit
TCP_DEBUG_LOG("TCP connection limit");
return NULL;
}
tcp_listener_t *listener = state->free_listener_list;
state->free_listener_list = listener->next;
// Initialize listener structure
listener->state = state;
listener->port = port;
listener->connections = NULL;
listener->connections_waiting_for_ack = NULL;
listener->connections_waiting_for_accept_head = NULL;
listener->connections_waiting_for_accept_tail = NULL;
listener->data = data;
listener->callback = callback;
// Push listener connection structure to the used list
listener->prev = NULL;
listener->next = state->used_listener_list;
if (state->used_listener_list)
state->used_listener_list->prev = listener;
state->used_listener_list = listener;
return listener;
}
void tcp_listener_destroy(tcp_listener_t *listener)
{
// TODO: Close all connections
tcp_state_t *state = listener->state;
// Pop listener from used list
{
// Update the reference to the listener of
// the one that precedes it in the list
if (listener->prev)
listener->prev->next = listener->next;
else
state->used_listener_list = listener->next;
// Update the reference to the listener of
// the one that follows it in the list
if (listener->next != NULL)
listener->next->prev = listener->prev;
}
// Push the listener in the free list
listener->next = state->free_listener_list;
state->free_listener_list = listener;
}
tcp_connection_t *tcp_listener_accept(tcp_listener_t *listener)
{
(void) listener;
if (!listener->connections_waiting_for_accept_tail)
return NULL;
tcp_connection_t *connection;
// Pop connection from the accept queue
{
connection = listener->connections_waiting_for_accept_tail;
if (connection->prev)
connection->prev->next = NULL;
else
listener->connections_waiting_for_accept_head = NULL;
listener->connections_waiting_for_accept_tail = connection->next;
}
// Push it to idle list
{
connection->prev = NULL;
connection->next = listener->connections;
if (listener->connections)
listener->connections->prev = connection;
listener->connections = connection;
}
return connection;
}
void tcp_connection_destroy(tcp_connection_t *connection)
{
// NOTE: This can only be called when the
// connection was accepted.
tcp_listener_t *listener = connection->listener;
// Pop connection from the idle connection list
if (connection->prev)
connection->prev->next = connection->next;
else
listener->connections = connection->next;
// Push it into the free connection list
tcp_state_t *state = listener->state;
connection->prev = NULL;
connection->next = state->free_connection_list;
state->free_connection_list = connection;
}
size_t tcp_connection_recv(tcp_connection_t *connection,
void *dst, size_t len)
{
size_t num = MIN(len, connection->in_used);
memcpy(dst, connection->in_buffer, num);
memmove(connection->in_buffer, connection->in_buffer + num, connection->in_used - num);
connection->in_used -= num;
return num;
}
static size_t
append_to_output_buffer(tcp_connection_t *connection,
const void *src, size_t len)
{
size_t num = MIN(len, TCP_OUTPUT_BUFFER_SIZE - connection->out_used);
memcpy(connection->out_buffer + connection->out_used, src, num);
connection->out_used += len;
return num;
}
static uint32_t calculate_checksum(const void *data, size_t size)
{
(void) data,
(void) size;
#warning "TODO: Calculate TCP checksum"
return 0;
}
static void
try_flushing_output_buffer(tcp_connection_t *connection)
{
tcp_state_t *tcp_state = connection->listener->state;
tcp_segment_t *segment = &connection->out_header;
segment->src_port = htons(connection->listener->port);
segment->dst_port = htons(connection->peer_port);
segment->seq_no = htons(connection->seq_no); // Should this be increased by the segment size?
segment->ack_no = htons(connection->ack_no);
segment->unused = 0;
segment->offset = 5; // No options
segment->flags = 0;
segment->window = htons(TCP_INPUT_BUFFER_SIZE - connection->in_used);
segment->checksum = 0; // Temporary value
segment->urgent_pointer = 0; // Don't support urgent data
segment->checksum = calculate_checksum(segment, sizeof(tcp_segment_t));
int sent_bytes = tcp_send(tcp_state, connection->peer_ip, segment, sizeof(tcp_segment_t) + connection->out_used);
if (sent_bytes < 0) {
// It wasn't possible to send out bytes. We'll try again later!
} else {
memmove(connection->out_buffer, connection->out_buffer + sent_bytes, connection->out_used - sent_bytes);
connection->out_used -= sent_bytes;
}
}
size_t tcp_connection_send(tcp_connection_t *connection, const void *src, size_t len)
{
size_t num = append_to_output_buffer(connection, src, len);
try_flushing_output_buffer(connection);
return num;
}
+96
View File
@@ -0,0 +1,96 @@
#include <stddef.h>
#include <stdint.h>
#include <endian.h>
#include "defs.h"
#define TCP_MAX_LISTENERS 32
#define TCP_MAX_SOCKETS 1024
#define TCP_INPUT_BUFFER_SIZE 1024
#define TCP_OUTPUT_BUFFER_SIZE 1024
typedef struct tcp_state_t tcp_state_t; // Predeclare for cyclic references
#define TCP_FLAG_FIN 0x01
#define TCP_FLAG_SYN 0x02
#define TCP_FLAG_RST 0x04
#define TCP_FLAG_PUSH 0x08
#define TCP_FLAG_ACK 0x10
#define TCP_FLAG_URG 0x20
typedef struct {
uint16_t src_port;
uint16_t dst_port;
uint32_t seq_no;
uint32_t ack_no;
#if __BYTE_ORDER == __LITTLE_ENDIAN
uint8_t unused: 4;
uint8_t offset: 4;
#endif
#if __BYTE_ORDER == __BIG_ENDIAN
uint8_t offset: 4;
uint8_t unused: 4;
#endif
uint8_t flags;
uint16_t window;
uint16_t checksum;
uint16_t urgent_pointer;
char payload[];
} tcp_segment_t;
typedef struct tcp_connection_t tcp_connection_t;
typedef struct tcp_listener_t tcp_listener_t;
struct tcp_listener_t {
tcp_state_t *state;
tcp_listener_t *prev;
tcp_listener_t *next;
uint16_t port;
tcp_connection_t *connections;
tcp_connection_t *connections_waiting_for_ack;
tcp_connection_t *connections_waiting_for_accept_head;
tcp_connection_t *connections_waiting_for_accept_tail;
void (*callback)(void*);
void *data;
};
struct tcp_connection_t {
tcp_listener_t *listener; // Listener that accepted this connection
tcp_connection_t *next;
tcp_connection_t *prev;
ip_address_t peer_ip;
uint16_t peer_port;
uint32_t seq_no;
uint32_t ack_no;
size_t in_used;
size_t out_used;
tcp_segment_t out_header; // There must be no padding between
char out_buffer[TCP_OUTPUT_BUFFER_SIZE]; // these two
char in_buffer[TCP_INPUT_BUFFER_SIZE];
};
static_assert(offsetof(tcp_connection_t, out_buffer) == offsetof(tcp_connection_t, out_header) + sizeof(tcp_segment_t));
typedef struct {
void *data;
int (*send)(void *data, ip_address_t ip, const void *src, size_t len);
} tcp_callbacks_t;
struct tcp_state_t {
tcp_callbacks_t callbacks;
tcp_connection_t *used_connection_list;
tcp_connection_t *free_connection_list;
tcp_connection_t connection_pool[TCP_MAX_SOCKETS];
tcp_listener_t *used_listener_list;
tcp_listener_t *free_listener_list;
tcp_listener_t listener_pool[TCP_MAX_LISTENERS];
};
void tcp_init(tcp_state_t *tcp_state, tcp_callbacks_t callbacks);
void tcp_free(tcp_state_t *tcp_state);
void tcp_seconds_passed(tcp_state_t *state, size_t seconds);
void tcp_process_segment(tcp_state_t *state, ip_address_t sender, tcp_segment_t *segment, size_t len);
tcp_listener_t *tcp_listener_create(tcp_state_t *state, uint16_t port, void *data, void (*callback)(void*));
void tcp_listener_destroy(tcp_listener_t *listener);
tcp_connection_t *tcp_listener_accept(tcp_listener_t *listener);
void tcp_connection_destroy(tcp_connection_t *connection);
size_t tcp_connection_recv(tcp_connection_t *connection, void *dst, size_t len);
size_t tcp_connection_send(tcp_connection_t *connection, const void *src, size_t len);
+56
View File
@@ -0,0 +1,56 @@
#include <stdio.h>
#include <microtcp.h>
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;
}
+161
View File
@@ -0,0 +1,161 @@
#include <stdio.h>
#include <string.h>
#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;
}
+146
View File
@@ -0,0 +1,146 @@
#include <stdio.h>
#include <string.h>
#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;
}
+36
View File
@@ -0,0 +1,36 @@
#include <stdbool.h>
#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);