Files
microtcp/src/microtcp.c
T

1453 lines
43 KiB
C

#include <time.h> // time()
#include <ctype.h>
#include <errno.h>
#include <string.h> // strerror()
#include <stdint.h>
#include <stdlib.h>
#ifndef MICROTCP_AMALGAMATION
# include "ip.h"
# include "arp.h"
# include "tcp.h"
# include "endian.h"
# include "microtcp.h"
# ifdef MICROTCP_BACKGROUND_THREAD
# include "tinycthread.h"
# endif
#endif
#ifdef MICROTCP_USING_TAP
#include <tuntap.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 { mtx_lock(&(mtcp)->lock); } while (0);
#define UNLOCK_WHEN_THREADED(mtcp) do { mtx_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
#ifdef MICROTCP_USING_MUX
typedef struct mux_entry_t mux_entry_t;
struct mux_entry_t {
mux_entry_t **mux_prev;
mux_entry_t *mux_next;
mux_entry_t **sock_prev;
mux_entry_t *sock_next;
microtcp_mux_t *mux; // This is set on initialization
// of the parent microtcp_mux_t
// and never changed.
microtcp_socket_t *sock;
void *userp;
int triggered_events;
int events_of_interest;
};
struct microtcp_mux_t {
microtcp_t *mtcp;
#ifdef MICROTCP_BACKGROUND_THREAD
cnd_t queue_not_empty;
#endif
mux_entry_t *free_list;
mux_entry_t *idle_list;
mux_entry_t *ready_queue_head;
mux_entry_t *ready_queue_tail;
mux_entry_t entries[MICROTCP_MAX_MUX_ENTRIES];
};
#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
union {
cnd_t something_to_accept;
struct {
cnd_t something_to_recv;
cnd_t something_to_send;
};
};
#endif
#ifdef MICROTCP_USING_MUX
mux_entry_t *mux_list;
#endif
};
struct microtcp_t {
time_t last_update_time;
#ifdef MICROTCP_BACKGROUND_THREAD
bool thread_should_stop;
thrd_t thread_id;
mtx_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_WOULDBLOCK: return "Can't executa e non-blocking call for this function";
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 = cpu_to_net_u16(ETHERNET_PROTOCOL_ARP);
// TODO: What about the 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 ip,
const slice_list_t *slices,
size_t num_slices)
{
microtcp_t *mtcp = data;
return ip_send_2(&mtcp->ip_state, IP_PROTOCOL_TCP, ip, true, slices, num_slices);
}
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 = cpu_to_net_u16(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 (net_to_cpu_u16(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[1024]; // This buffer is the bottleneck for the
// maximum packet size that can be processed.
// 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_WHEN_THREADED(mtcp);
}
#ifdef MICROTCP_BACKGROUND_THREAD
static int loop(void *data)
{
microtcp_t *mtcp = data;
while (!mtcp->thread_should_stop)
microtcp_step(mtcp);
return 0;
}
#endif
static bool is_hex_digit(char c)
{
return (c >= '0' && c <= '9')
|| (c >= 'a' && c <= 'f')
|| (c >= 'A' && c <= 'F');
}
static int int_from_hex_digit(char c)
{
assert(is_hex_digit(c));
if (c >= 'A' || c <= 'F')
return c - 'A' + 10;
if (c >= 'a' || c <= 'f')
return c - 'a' + 10;
return c - '0';
}
static bool parse_mac(const char *src, size_t len,
mac_address_t *mac)
{
if (src == NULL || len != 17
|| !is_hex_digit(src[0])
|| !is_hex_digit(src[1])
|| src[2] != ':'
|| !is_hex_digit(src[3])
|| !is_hex_digit(src[4])
|| src[5] != ':'
|| !is_hex_digit(src[6])
|| !is_hex_digit(src[7])
|| src[8] != ':'
|| !is_hex_digit(src[9])
|| !is_hex_digit(src[10])
|| src[11] != ':'
|| !is_hex_digit(src[12])
|| !is_hex_digit(src[13])
|| src[14] != ':'
|| !is_hex_digit(src[15])
|| !is_hex_digit(src[16]))
return false;
static const char max_char_map[] = "0123456789ABCDEF";
if (mac) {
mac->data[0] = max_char_map[int_from_hex_digit(src[ 0])] << 4
| max_char_map[int_from_hex_digit(src[ 1])];
mac->data[1] = max_char_map[int_from_hex_digit(src[ 3])] << 4
| max_char_map[int_from_hex_digit(src[ 4])];
mac->data[2] = max_char_map[int_from_hex_digit(src[ 6])] << 4
| max_char_map[int_from_hex_digit(src[ 7])];
mac->data[3] = max_char_map[int_from_hex_digit(src[ 9])] << 4
| max_char_map[int_from_hex_digit(src[10])];
mac->data[4] = max_char_map[int_from_hex_digit(src[12])] << 4
| max_char_map[int_from_hex_digit(src[13])];
mac->data[5] = max_char_map[int_from_hex_digit(src[15])] << 4
| max_char_map[int_from_hex_digit(src[16])];
}
return true;
}
static mac_address_t generate_random_mac()
{
mac_address_t mac = {
.data = {
rand() & 0xff,
rand() & 0xff,
rand() & 0xff,
rand() & 0xff,
rand() & 0xff,
rand() & 0xff,
},
};
return mac;
}
static bool parse_ip(const char *ip, ip_address_t *parsed_ip)
{
size_t len = strlen(ip);
size_t i = 0;
uint32_t value = 0;
for (size_t k = 0; k < 4; k++) {
if (i == len || !isdigit(ip[i]))
return false;
int n = 0; // Used to represent a byte, but it's larger
// to detect overflows.
do {
// Convert character to number
int digit = ip[i] - '0';
if (n > (UINT8_MAX - digit)/10)
// Adding this digit would make the
// byte overflow, so it can't be part
// of the octet.
break;
n = n * 10 + digit;
i++;
} while (i < len && isdigit(ip[i]));
assert(n >= 0 && n <= UINT8_MAX);
value = (value << 8) | (uint8_t) n;
// If this isn't the last octet and there is no
// dot following it, the address is invalid.
if (k < 3) {
if (i == len || ip[i] != '.')
return false;
i++; // Consume the dot.
}
}
if (i < len)
// source string contains something
// other than the address in it.
return false;
*parsed_ip = cpu_to_net_u32(value);
return true;
}
microtcp_t *microtcp_create_using_callbacks(const char *ip, const char *mac,
microtcp_callbacks_t callbacks)
{
mac_address_t parsed_mac;
if (mac == NULL) {
// Generate a random MAC
parsed_mac = generate_random_mac();
} else {
if (!parse_mac(mac, mac ? strlen(mac) : 0, &parsed_mac))
return NULL;
}
ip_address_t parsed_ip;
if (!parse_ip(ip, &parsed_ip))
return NULL;
microtcp_t *mtcp = malloc(sizeof(microtcp_t));
if (mtcp == NULL)
return NULL;
mtcp->ip = parsed_ip;
mtcp->mac = parsed_mac;
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, parsed_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, parsed_ip, parsed_mac, mtcp, send_arp_packet);
tcp_init(&mtcp->tcp_state, parsed_ip, (tcp_callbacks_t) {
.data = mtcp,
.send = send_tcp_segment,
});
use_a_buffer(mtcp);
#ifdef MICROTCP_BACKGROUND_THREAD
{
if (mtx_init(&mtcp->lock, mtx_plain) != thrd_success) {
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 (thrd_create(&mtcp->thread_id, loop, mtcp) != thrd_success) {
ip_free(&mtcp->ip_state);
arp_free(&mtcp->arp_state);
tcp_free(&mtcp->tcp_state);
mtx_destroy(&mtcp->lock);
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;
}
#ifdef MICROTCP_USING_TAP
static void log_callback_for_tuntap_library(int level, const char *errmsg)
{
const char *name;
switch(level) {
case TUNTAP_LOG_DEBUG : name = "Debug"; break;
case TUNTAP_LOG_INFO : name = "Info"; break;
case TUNTAP_LOG_NOTICE: name = "Notice"; break;
case TUNTAP_LOG_WARN : name = "Warning"; break;
case TUNTAP_LOG_ERR : name = "Error"; break;
case TUNTAP_LOG_NONE:
default:
name = NULL;
break;
}
if (name == NULL) {
MICROTCP_DEBUG_LOG("%s (from the tap library)", errmsg);
} else {
MICROTCP_DEBUG_LOG("[%s] %s (from the tap library)", name, errmsg);
}
}
bool microtcp_callbacks_create_for_tap(const char *ip, const char *mac,
microtcp_callbacks_t *callbacks)
{
assert(ip);
struct device *dev = tuntap_init();
if (!dev)
return false;
// This must be set AFTER tuntap_init because
// it sets the callback function to the default
// callback which writes to stderr.
tuntap_log_set_cb(log_callback_for_tuntap_library);
int netmask = 24; // TODO: Make this configurable
if (tuntap_start(dev, TUNTAP_MODE_ETHERNET, TUNTAP_ID_ANY))
goto cleanup;
tuntap_set_ip(dev, ip, netmask);
tuntap_set_hwaddr(dev, mac ? mac : "random");
if (tuntap_up(dev))
goto cleanup;
*callbacks = (microtcp_callbacks_t) {
.data = dev,
.free = (void(*)(void*)) tuntap_release,
.recv = (int(*)(void*, void*, size_t)) tuntap_read,
.send = (int(*)(void*, const void*, size_t)) tuntap_write,
};
return true;
cleanup:
tuntap_release(dev);
return false;
}
microtcp_t *microtcp_create(const char *tap_ip, const char *stack_ip,
const char *tap_mac, const char *stack_mac)
{
microtcp_callbacks_t callbacks;
if (!microtcp_callbacks_create_for_tap(tap_ip, tap_mac, &callbacks))
return NULL;
microtcp_t *mtcp = microtcp_create_using_callbacks(stack_ip, stack_mac, callbacks);
if (!mtcp)
callbacks.free(callbacks.data);
return mtcp;
}
#endif
void microtcp_destroy(microtcp_t *mtcp)
{
#ifdef MICROTCP_BACKGROUND_THREAD
MICROTCP_DEBUG_LOG("Stopping thread");
mtcp->thread_should_stop = true;
thrd_join(mtcp->thread_id, NULL);
mtx_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 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;
}
#ifdef MICROTCP_USING_MUX
static void
signal_events_to_muxes_associated_to_socket(microtcp_socket_t *socket, int events);
#endif
static void ready_to_accept(void *data)
{
microtcp_socket_t *socket = data;
(void) socket;
#ifdef MICROTCP_BACKGROUND_THREAD
cnd_signal(&socket->something_to_accept);
#endif
#ifdef MICROTCP_USING_MUX
MICROTCP_DEBUG_LOG("Signaling ACCEPT to muxes");
signal_events_to_muxes_associated_to_socket(socket, MICROTCP_MUX_ACCEPT);
#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, false, ready_to_accept);
if (listener == NULL) {
// FIXME: 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_USING_MUX
socket->mux_list = NULL;
#endif
#ifdef MICROTCP_BACKGROUND_THREAD
if (cnd_init(&socket->something_to_accept) != thrd_success) {
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);
{
#ifdef MICROTCP_USING_MUX
// Unregister from all multiplexers
while (socket->mux_list) {
// The unregister operation only has
// an effect when all of the triggered
// events of the socket are consumed,
// so to unregister immediately we need
// to untrigger the events
socket->mux_list->triggered_events = 0;
microtcp_mux_unregister(socket->mux_list->mux, socket, ~0);
}
#endif
switch (socket->type) {
case SOCKET_LISTENER:
#ifdef MICROTCP_BACKGROUND_THREAD
cnd_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);
}
static void ready_to_recv(void *data)
{
microtcp_socket_t *socket = data;
(void) socket;
#ifdef MICROTCP_BACKGROUND_THREAD
cnd_signal(&socket->something_to_recv);
#endif
#ifdef MICROTCP_USING_MUX
MICROTCP_DEBUG_LOG("Signaling RECV to muxes");
signal_events_to_muxes_associated_to_socket(socket, MICROTCP_MUX_RECV);
#endif
}
static void ready_to_send(void *data)
{
microtcp_socket_t *socket = data;
(void) socket;
#ifdef MICROTCP_BACKGROUND_THREAD
cnd_signal(&socket->something_to_send);
#endif
#ifdef MICROTCP_USING_MUX
MICROTCP_DEBUG_LOG("Signaling SEND to muxes");
signal_events_to_muxes_associated_to_socket(socket, MICROTCP_MUX_SEND);
#endif
}
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
}
socket2 = pop_socket_struct_from_free_list(mtcp);
if (!socket2) {
errcode2 = MICROTCP_ERRCODE_SOCKETLIMIT;
goto unlock_and_exit; // Socket limit reached
}
tcp_connection_t *connection = tcp_listener_accept(socket->listener, socket2, ready_to_recv, ready_to_send);
#ifdef MICROTCP_BACKGROUND_THREAD
while (!connection && !no_block) {
if (cnd_wait(&socket->something_to_accept, &mtcp->lock) != thrd_success) {
errcode2 = MICROTCP_ERRCODE_BADCONDVAR;
push_unlinked_socket_into_free_list(mtcp, socket2);
goto unlock_and_exit;
}
connection = tcp_listener_accept(socket->listener, socket2, ready_to_recv, ready_to_send);
}
#else
if (!connection) {
if (no_block)
errcode2 = MICROTCP_ERRCODE_WOULDBLOCK;
else
errcode2 = MICROTCP_ERRCODE_CANTBLOCK;
push_unlinked_socket_into_free_list(mtcp, socket2);
goto unlock_and_exit;
}
#endif
socket2->mtcp = mtcp;
socket2->prev = NULL;
socket2->next = NULL;
socket2->type = SOCKET_CONNECTION;
socket2->connection = connection;
#ifdef MICROTCP_USING_MUX
socket2->mux_list = NULL;
#endif
#ifdef MICROTCP_BACKGROUND_THREAD
if (cnd_init(&socket2->something_to_recv) != thrd_success) {
errcode2 = MICROTCP_ERRCODE_BADCONDVAR;
push_unlinked_socket_into_free_list(mtcp, socket2);
goto unlock_and_exit;
}
if (cnd_init(&socket2->something_to_send) != thrd_success) {
errcode2 = MICROTCP_ERRCODE_BADCONDVAR;
cnd_destroy(&socket2->something_to_recv);
push_unlinked_socket_into_free_list(mtcp, socket2);
goto unlock_and_exit;
}
#endif
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,
bool no_block,
microtcp_errcode_t *errcode)
{
if (!socket || socket->type != SOCKET_CONNECTION) {
if (errcode)
*errcode = MICROTCP_ERRCODE_NOTCONNECTION;
return 0;
}
size_t num;
microtcp_t *mtcp = socket->mtcp;
microtcp_errcode_t errcode2 = MICROTCP_ERRCODE_NONE;
LOCK_WHEN_THREADED(mtcp);
{
num = tcp_connection_recv(socket->connection, dst, len);
#ifdef MICROTCP_BACKGROUND_THREAD
while (num == 0 && !no_block) {
if (cnd_wait(&socket->something_to_recv, &mtcp->lock) != thrd_success) {
errcode2 = MICROTCP_ERRCODE_BADCONDVAR;
goto unlock_and_exit;
}
num = tcp_connection_recv(socket->connection, dst, len);
}
#endif
if (num == 0) {
if (no_block)
errcode2 = MICROTCP_ERRCODE_WOULDBLOCK;
else
errcode2 = MICROTCP_ERRCODE_CANTBLOCK;
}
}
goto unlock_and_exit; // Warning
unlock_and_exit:
UNLOCK_WHEN_THREADED(mtcp);
if (errcode)
*errcode = errcode2;
return num;
}
size_t microtcp_send(microtcp_socket_t *socket,
const void *src, size_t len,
bool no_block,
microtcp_errcode_t *errcode)
{
if (!socket || socket->type != SOCKET_CONNECTION) {
if (errcode)
*errcode = MICROTCP_ERRCODE_NOTCONNECTION;
return 0;
}
size_t num;
microtcp_t *mtcp = socket->mtcp;
microtcp_errcode_t errcode2 = MICROTCP_ERRCODE_NONE;
LOCK_WHEN_THREADED(mtcp);
{
num = tcp_connection_send(socket->connection, src, len);
#ifdef MICROTCP_BACKGROUND_THREAD
while (num == 0 && !no_block) {
if (cnd_wait(&socket->something_to_send, &mtcp->lock) != thrd_success) {
errcode2 = MICROTCP_ERRCODE_BADCONDVAR;
goto unlock_and_exit;
}
num = tcp_connection_send(socket->connection, src, len);
}
#endif
if (num == 0) {
if (no_block)
errcode2 = MICROTCP_ERRCODE_WOULDBLOCK;
else
errcode2 = MICROTCP_ERRCODE_CANTBLOCK;
}
}
goto unlock_and_exit; // Warning
unlock_and_exit:
UNLOCK_WHEN_THREADED(mtcp);
if (errcode)
*errcode = errcode2;
return num;
}
#ifdef MICROTCP_USING_MUX
microtcp_mux_t *microtcp_mux_create(microtcp_t *mtcp)
{
microtcp_mux_t *mux = malloc(sizeof(microtcp_mux_t));
if (!mux)
return NULL;
mux->mtcp = mtcp;
// Build the free list
static_assert(MICROTCP_MAX_MUX_ENTRIES > 1);
const int max = MICROTCP_MAX_MUX_ENTRIES;
for (int i = 1; i < max-1; i++) {
mux->entries[i].mux = mux; // This will be never changed
mux->entries[i].mux_prev = &mux->entries[i-1].mux_next;
mux->entries[i].mux_next = &mux->entries[i+1];
}
mux->entries[0].mux = mux; // Never changed
mux->entries[0].mux_prev = &mux->free_list;
mux->entries[0].mux_next = &mux->entries[1];
mux->entries[max-1].mux = mux; // Never changed
mux->entries[max-1].mux_prev = &mux->entries[max-2].mux_next;
mux->entries[max-1].mux_next = NULL;
mux->idle_list = NULL;
mux->free_list = mux->entries;
mux->ready_queue_head = NULL;
mux->ready_queue_tail = NULL;
#ifdef MICROTCP_BACKGROUND_THREAD
if (cnd_init(&mux->queue_not_empty) != thrd_success) {
free(mux);
return NULL;
}
#endif
return mux;
}
static bool mux_poll(microtcp_mux_t *mux, microtcp_muxevent_t *ev);
void microtcp_mux_destroy(microtcp_mux_t *mux)
{
// Unregister all idle sockets
// Idle entries don't have pending events
// to deliver so by unregistering them the
// entry is unlinked.
while (mux->idle_list)
microtcp_mux_unregister(mux, mux->idle_list->sock, ~0);
// Consume all previously reported events
// to make sure that when unregistering
// the entries are actually removed
while (mux_poll(mux, NULL));
// Unreagister all sockets that have events
while (mux->ready_queue_head) {
mux_entry_t *entry = mux->ready_queue_head;
microtcp_mux_unregister(mux, entry->sock, ~0);
// Since all events were consumed beforehand
// we're sure the entry was removed.
assert(entry != mux->ready_queue_head);
}
#ifdef MICROTCP_BACKGROUND_THREAD
cnd_destroy(&mux->queue_not_empty);
#endif
free(mux);
}
static mux_entry_t*
find_socket_and_mux_entry(microtcp_mux_t *mux, microtcp_socket_t *sock)
{
mux_entry_t *entry = sock->mux_list;
while (entry) {
if (entry->mux == mux)
break;
entry = entry->sock_next;
}
return entry;
}
static void
move_mux_entry_to_free_list(mux_entry_t *entry)
{
microtcp_mux_t *mux = entry->mux;
// If the entry is in a list, unlink it
if (mux->ready_queue_tail == entry)
mux->ready_queue_tail = entry->mux_next;
if (entry->mux_prev)
*entry->mux_prev = entry->mux_next;
if (entry->sock_prev)
*entry->sock_prev = entry->sock_next;
// Put the structure into the free list
entry->mux_prev = &mux->free_list;
entry->mux_next = mux->free_list;
if (mux->free_list)
mux->free_list->mux_prev = &entry->mux_next;
mux->free_list = entry;
}
static void
move_mux_entry_to_idle_list(mux_entry_t *entry)
{
microtcp_mux_t *mux = entry->mux;
// To be moved to the idle list the entry
// must be associated to a socket so it
// must be in a socket mux list, therefore
// it must be true that
assert(entry->sock_prev); // not null iff the entry is in a mux list
// Make sure the entry is unlinked relative
// to the lists in the mux
if (mux->ready_queue_tail == entry)
mux->ready_queue_tail = entry->mux_next;
if (entry->mux_prev)
*entry->mux_prev = entry->mux_next;
// Now actually insert it into the idle list
entry->mux_prev = &mux->idle_list;
entry->mux_next = mux->idle_list;
if (mux->idle_list)
mux->idle_list->mux_prev = &entry->mux_next;
mux->idle_list = entry;
}
bool microtcp_mux_unregister(microtcp_mux_t *mux, microtcp_socket_t *sock, int events)
{
LOCK_WHEN_THREADED(mux->mtcp);
// There's no need to check that mux
// and socket have the same mtcp because
// if it's different it will result that
// the socket isn't registered into the
// mux.
mux_entry_t *entry = find_socket_and_mux_entry(mux, sock);
if (!entry) {
// This socket wasn't registered into the mux
UNLOCK_WHEN_THREADED(mux->mtcp);
return false;
}
// Unset the events of interest
entry->events_of_interest &= ~events;
if (entry->triggered_events) {
// NOTE: Since we modified "events_of_interest"
// but not "triggered_events", any previously
// triggered events that were now unregistered
// will still be delivered to the user.
//
// Though when events are delivered, if all
// events registered were all unregistered,
// the socket is removed from the mux.
} else
// No events were previously reported so we can
// move the entry to the free list.
move_mux_entry_to_free_list(entry);
UNLOCK_WHEN_THREADED(mux->mtcp);
return true;
}
bool microtcp_mux_register(microtcp_mux_t *mux, microtcp_socket_t *sock, int events, void *userp)
{
LOCK_WHEN_THREADED(mux->mtcp);
if (mux->mtcp != sock->mtcp) {
UNLOCK_WHEN_THREADED(mux->mtcp);
return false; // mux and socket are associated to different microtcp stacks
}
if (events == 0) {
UNLOCK_WHEN_THREADED(mux->mtcp);
return true; // Nothing to be done
}
mux_entry_t *entry = find_socket_and_mux_entry(mux, sock);
if (!entry) {
// This is the first time that the socket is registered.
// Create an entry for it
if (mux->free_list == NULL) {
// The entry limit was reached.
// It's impossible to register the socket at this time
UNLOCK_WHEN_THREADED(mux->mtcp);
return false;
}
// Pop from the free list
entry = mux->free_list;
*entry->mux_prev = entry->mux_next;
// Push it into the idle list of the mux
entry->mux_prev = &mux->idle_list;
entry->mux_next = mux->idle_list;
if (mux->idle_list)
mux->idle_list->mux_prev = &entry->mux_next;
mux->idle_list = entry;
// Push it into the socket mux list
entry->sock_prev = &sock->mux_list;
entry->sock_next = sock->mux_list;
if (sock->mux_list)
sock->mux_list->sock_prev = &entry->sock_next;
sock->mux_list = entry;
// Initialize the entry
entry->sock = sock;
entry->userp = userp;
entry->triggered_events = 0;
entry->events_of_interest = 0;
// entry->mux = mux; This isn't necessary because the mux field
// is initialized once with the mux and never
// changed.
}
entry->events_of_interest |= events;
UNLOCK_WHEN_THREADED(mux->mtcp);
return true;
}
static bool mux_poll(microtcp_mux_t *mux, microtcp_muxevent_t *ev)
{
if (!mux->ready_queue_head)
return false; // No events occurred
// Get the tail of the queue (without popping it)
mux_entry_t *entry = mux->ready_queue_head;
// If this socket was in the ready queue
// it must have triggered events
assert(entry->triggered_events);
if (ev) {
ev->userp = entry->userp;
ev->events = entry->triggered_events;
ev->socket = entry->sock;
}
// Unmark events as triggered
entry->triggered_events = 0;
if (entry->events_of_interest == 0)
// All events were unregistered.
// We can remove the socket from the mux.
move_mux_entry_to_free_list(entry);
else
// The socket wasn't unregistered or
// wasn't unregistered completely so
// we put the entry into the idle list
move_mux_entry_to_idle_list(entry);
return true;
}
bool microtcp_mux_wait(microtcp_mux_t *mux, microtcp_muxevent_t *ev)
{
#ifdef MICROTCP_BACKGROUND_THREAD
LOCK_WHEN_THREADED(mux->mtcp);
while (!mux_poll(mux, ev)) {
MICROTCP_DEBUG_LOG("Multiplexer waiting for an event");
if (cnd_wait(&mux->queue_not_empty, &mux->mtcp->lock) != thrd_success)
abort();
MICROTCP_DEBUG_LOG("Multiplexer woke up for an event");
}
UNLOCK_WHEN_THREADED(mux->mtcp);
return true;
#else
return mux_poll(mux, ev);
#endif
}
static void
signal_events_to_muxes_associated_to_socket(microtcp_socket_t *socket, int events)
{
// (This function is called by the socket and not the mux)
assert(events); // If no events need to be signaled then
// this function has no reason to be called.
MICROTCP_DEBUG_LOG("Socket about to signal to multiplexers");
mux_entry_t *entry = socket->mux_list;
while (entry) {
microtcp_mux_t *mux = entry->mux;
// Mask the bitmask of triggered events [events] with
// the bitmask of events that this multiplexer is
// interested in.
int newly_triggered_events = events & entry->events_of_interest;
if (!newly_triggered_events)
MICROTCP_DEBUG_LOG("MUX not interested in these events");
// If there are no previously triggered events by this
// socket and the socket just generated some events the
// mux is interested in, then we need to move the socket-mux
// structure from the idle list to the ready queue of the mux.
bool first_event_of_socket_in_mux = (entry->triggered_events == 0) && newly_triggered_events;
entry->triggered_events |= newly_triggered_events;
if (first_event_of_socket_in_mux) {
// Is this the first socket structure of the muxes
// ready queue? If it is, we'll need to wake it up
bool queue_was_empty = (mux->ready_queue_head == NULL);
// Unlink it from the idle list
*entry->mux_prev = entry->mux_next;
if (entry->mux_next)
entry->mux_next->mux_prev = entry->mux_prev;
// Add it to the queue
if (mux->ready_queue_tail)
entry->mux_prev = &mux->ready_queue_tail->mux_next;
else {
entry->mux_prev = &mux->ready_queue_head;
mux->ready_queue_head = entry;
}
entry->mux_next = NULL;
mux->ready_queue_tail = entry;
#ifdef MICROTCP_BACKGROUND_THREAD
MICROTCP_DEBUG_LOG("Signaling event to multiplexer");
if (queue_was_empty)
cnd_signal(&mux->queue_not_empty);
MICROTCP_DEBUG_LOG("Signaled event to multiplexer");
#else
(void) queue_was_empty;
#endif
}
entry = entry->sock_next;
}
MICROTCP_DEBUG_LOG("Socket signaled to multiplexers");
}
#endif