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
+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;
}