new I/O multiplexer api; http echo server example draft to test the mux api; the library is now released as an amalgamation; recv/send now return the error MICROTCP_ERRCODE_WOULDBLOCK in a similar way to the BSD socket api; moved tests/loop2.c to examples/echo_tcp.c

This commit is contained in:
cozis
2023-05-19 20:24:13 +02:00
parent 2a8741afe4
commit 464a1ee724
27 changed files with 3370 additions and 690 deletions
+3
View File
@@ -115,8 +115,11 @@ struct ethhdr {
*/
#include <stdbool.h>
#ifndef MICROTCP_AMALGAMATION
#include "endian.h"
#include "arp.h"
#endif
#ifdef ARP_DEBUG
#include <stdio.h>
+3
View File
@@ -1,7 +1,10 @@
#include <assert.h>
#include <stdint.h>
#include <stddef.h>
#ifndef MICROTCP_AMALGAMATION
#include "defs.h"
#endif
#define ARP_MAX_PENDING_REQUESTS 32
#define ARP_TRANSLATION_TABLE_SIZE 128
+2
View File
@@ -1,4 +1,6 @@
#ifndef MICROTCP_AMALGAMATION
#include "endian.h"
#endif
bool cpu_is_little_endian(void)
{
+3
View File
@@ -1,6 +1,9 @@
#include <string.h>
#ifndef MICROTCP_AMALGAMATION
#include "endian.h"
#include "icmp.h"
#endif
#ifdef ICMP_DEBUG
#include <stdio.h>
+3
View File
@@ -1,5 +1,8 @@
#include <stddef.h>
#ifndef MICROTCP_AMALGAMATION
#include "defs.h"
#endif
typedef struct {
void *output_ptr;
+3
View File
@@ -1,6 +1,9 @@
#include <string.h>
#ifndef MICROTCP_AMALGAMATION
#include "endian.h"
#include "ip.h"
#endif
#ifdef IP_DEBUG
#include <stdio.h>
+3
View File
@@ -1,8 +1,11 @@
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#ifndef MICROTCP_AMALGAMATION
#include "defs.h"
#include "icmp.h"
#endif
#define IP_PLUGGED_PROTOCOLS_MAX 4
+440 -35
View File
@@ -4,20 +4,22 @@
#include <string.h> // strerror()
#include <stdint.h>
#include <stdlib.h>
#include "ip.h"
#include "arp.h"
#include "tcp.h"
#include "endian.h"
#include <microtcp.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_BACKGROUND_THREAD
#include "tinycthread.h"
#endif
#ifdef MICROTCP_DEBUG
#include <stdio.h>
#define MICROTCP_DEBUG_LOG(fmt, ...) do { fprintf(stderr, "MICROTCP :: " fmt "\n", ## __VA_ARGS__); } while (0);
@@ -33,6 +35,35 @@
#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;
@@ -56,6 +87,7 @@ struct microtcp_socket_t {
tcp_listener_t *listener;
tcp_connection_t *connection;
};
#ifdef MICROTCP_BACKGROUND_THREAD
union {
cnd_t something_to_accept;
@@ -65,6 +97,10 @@ struct microtcp_socket_t {
};
};
#endif
#ifdef MICROTCP_USING_MUX
mux_entry_t *mux_list;
#endif
};
struct microtcp_t {
@@ -105,6 +141,7 @@ const char *microtcp_strerror(microtcp_errcode_t errcode)
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 "???";
@@ -367,7 +404,7 @@ void microtcp_step(microtcp_t *mtcp)
// 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.
// threads the ability to progress in the mean time.
int size = mtcp->callbacks.recv(mtcp->callbacks.data, packet, sizeof(packet));
if (size < 0)
return;
@@ -671,9 +708,9 @@ bool microtcp_callbacks_create_for_tap(const char *ip, const char *mac,
*callbacks = (microtcp_callbacks_t) {
.data = dev,
.free = tuntap_release,
.recv = tuntap_read,
.send = tuntap_write,
.free = (void(*)(void*)) tuntap_release,
.recv = (int(*)(void*, void*, size_t)) tuntap_read,
.send = (int(*)(void*, const void*, size_t)) tuntap_write,
};
return true;
@@ -758,17 +795,26 @@ push_unlinked_socket_into_free_list(microtcp_t *mtcp, microtcp_socket_t *socket)
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)
{
#ifdef MICROTCP_BACKGROUND_THREAD
microtcp_socket_t *socket = data;
(void) socket;
#ifdef MICROTCP_BACKGROUND_THREAD
cnd_signal(&socket->something_to_accept);
#else
(void) data;
#endif
#ifdef MICROTCP_USING_MUX
signal_events_to_muxes_associated_to_socket(socket, MICROTCP_MUX_ACCEPT);
#endif
}
microtcp_socket_t *microtcp_open(microtcp_t *mtcp, uint16_t port,
microtcp_socket_t *microtcp_open(microtcp_t *mtcp, uint16_t port,
microtcp_errcode_t *errcode)
{
microtcp_errcode_t errcode2 = MICROTCP_ERRCODE_NONE;
@@ -795,6 +841,10 @@ microtcp_socket_t *microtcp_open(microtcp_t *mtcp, uint16_t port,
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;
@@ -819,6 +869,8 @@ void microtcp_close(microtcp_socket_t *socket)
microtcp_t *mtcp = socket->mtcp;
#warning "sockets should unregister from all multiplexers"
LOCK_WHEN_THREADED(mtcp);
{
switch (socket->type) {
@@ -843,21 +895,29 @@ void microtcp_close(microtcp_socket_t *socket)
static void ready_to_recv(void *data)
{
#ifdef MICROTCP_BACKGROUND_THREAD
microtcp_socket_t *socket = data;
(void) socket;
#ifdef MICROTCP_BACKGROUND_THREAD
cnd_signal(&socket->something_to_recv);
#else
(void) data;
#endif
#ifdef MICROTCP_USING_MUX
signal_events_to_muxes_associated_to_socket(socket, MICROTCP_MUX_RECV);
#endif
}
static void ready_to_send(void *data)
{
#ifdef MICROTCP_BACKGROUND_THREAD
microtcp_socket_t *socket = data;
(void) socket;
#ifdef MICROTCP_BACKGROUND_THREAD
cnd_signal(&socket->something_to_send);
#else
(void) data;
#endif
#ifdef MICROTCP_USING_MUX
signal_events_to_muxes_associated_to_socket(socket, MICROTCP_MUX_SEND);
#endif
}
@@ -886,14 +946,20 @@ microtcp_socket_t *microtcp_accept(microtcp_socket_t *socket,
#ifdef MICROTCP_BACKGROUND_THREAD
while (!connection && !no_block) {
if (cnd_wait(&socket->something_to_accept, &mtcp->lock) != thrd_success)
abort();
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 && !no_block) {
if (!connection) {
if (no_block)
errcode2 = MICROTCP_ERRCODE_WOULDBLOCK;
else
errcode2 = MICROTCP_ERRCODE_CANTBLOCK;
push_unlinked_socket_into_free_list(mtcp, socket2);
errcode2 = MICROTCP_ERRCODE_CANTBLOCK;
goto unlock_and_exit;
}
#endif
@@ -904,6 +970,10 @@ microtcp_socket_t *microtcp_accept(microtcp_socket_t *socket,
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;
@@ -953,13 +1023,18 @@ size_t microtcp_recv(microtcp_socket_t *socket,
#ifdef MICROTCP_BACKGROUND_THREAD
while (num == 0 && !no_block) {
if (cnd_wait(&socket->something_to_recv, &mtcp->lock) != thrd_success)
abort();
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);
}
#else
if (num == 0 && !no_block) {
errcode2 = MICROTCP_ERRCODE_CANTBLOCK;
if (num == 0) {
if (no_block)
errcode2 = MICROTCP_ERRCODE_WOULDBLOCK;
else
errcode2 = MICROTCP_ERRCODE_CANTBLOCK;
goto unlock_and_exit;
}
#endif
@@ -994,18 +1069,348 @@ size_t microtcp_send(microtcp_socket_t *socket,
#ifdef MICROTCP_BACKGROUND_THREAD
while (num == 0 && !no_block) {
if (cnd_wait(&socket->something_to_send, &mtcp->lock) != thrd_success)
abort();
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);
}
#else
if (num == 0 && !no_block)
errcode2 = MICROTCP_ERRCODE_CANTBLOCK;
if (num == 0) {
if (no_block)
errcode2 = MICROTCP_ERRCODE_WOULDBLOCK;
else
errcode2 = MICROTCP_ERRCODE_CANTBLOCK;
goto unlock_and_exit;
}
#endif
}
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;
}
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 (microtcp_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 (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)
{
// 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 (entry->mux_prev)
*entry->mux_prev = entry->mux_next;
// Now actually insert it into the idle list
microtcp_mux_t *mux = entry->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;
}
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;
// 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;
// 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;
}
bool microtcp_mux_poll(microtcp_mux_t *mux, microtcp_muxevent_t *ev)
{
LOCK_WHEN_THREADED(mux->mtcp);
// Get the tail of the queue (without popping it)
mux_entry_t *entry = mux->ready_queue_tail;
if (!entry) {
UNLOCK_WHEN_THREADED(mux->mtcp);
return false; // No events occurred
}
// 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);
UNLOCK_WHEN_THREADED(mux->mtcp);
return true;
}
bool microtcp_mux_wait(microtcp_mux_t *mux, microtcp_muxevent_t *ev)
{
#ifdef MICROTCP_BACKGROUND_THREAD
LOCK_WHEN_THREADED(mux->mtcp);
while (!microtcp_mux_poll(mux, ev))
if (cnd_wait(&mux->queue_not_empty, &mux->mtcp->lock) != thrd_success)
abort();
UNLOCK_WHEN_THREADED(mux->mtcp);
return true;
#else
return microtcp_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.
mux_entry_t *entry = socket->mux_list;
while (entry) {
microtcp_mux_t *mux = entry->mux;
bool first_event_of_socket = (entry->triggered_events == 0);
entry->triggered_events |= events & entry->events_of_interest;
if (first_event_of_socket) {
bool queue_was_empty = (mux->ready_queue_head == NULL);
// No entries were already triggered so
// it's necessary to move the entry from
// the mux's idle list to ready queue
// Unlink it from the idle queue
*entry->mux_prev = entry->mux_next;
if (entry->mux_next)
entry->mux_next->mux_prev = entry->mux_prev;
// Add it to the queue
entry->mux_prev = &mux->ready_queue_head;
entry->mux_next = mux->ready_queue_head;
if (mux->ready_queue_head)
mux->ready_queue_head->mux_prev = &entry->mux_next;
else
mux->ready_queue_tail = entry;
mux->ready_queue_head = entry;
#ifdef MICROTCP_BACKGROUND_THREAD
if (queue_was_empty)
cnd_signal(&mux->queue_not_empty);
#else
(void) queue_was_empty;
#endif
}
entry = entry->sock_next;
}
}
#endif
+10 -5
View File
@@ -1,14 +1,17 @@
#include <string.h>
#include <stdbool.h>
#include <stdalign.h>
#include "endian.h"
#include "tcp.h"
#ifndef MICROTCP_AMALGAMATION
# include "endian.h"
# include "tcp.h"
#endif
#ifdef TCP_DEBUG
#include <stdio.h>
#define TCP_DEBUG_LOG(fmt, ...) fprintf(stderr, "TCP :: " fmt "\n", ## __VA_ARGS__)
# include <stdio.h>
# define TCP_DEBUG_LOG(fmt, ...) fprintf(stderr, "TCP :: " fmt "\n", ## __VA_ARGS__)
#else
#define TCP_DEBUG_LOG(...)
# define TCP_DEBUG_LOG(...)
#endif
#define SEGMENT_OFFSET(seg) (cpu_is_little_endian() ? (seg)->offset2 : (seg)->offset1)
@@ -532,6 +535,8 @@ tcp_listener_create(tcp_state_t *state, uint16_t port, void *callback_data,
void tcp_listener_destroy(tcp_listener_t *listener)
{
#warning "The previously accepted connections should't be closed with their listener.. I think"
// TODO: Close all connections
tcp_state_t *state = listener->state;
+3 -1
View File
@@ -1,6 +1,9 @@
#include <stddef.h>
#include <stdint.h>
#ifndef MICROTCP_AMALGAMATION
#include "defs.h"
#endif
#define TCP_MAX_LISTENERS 32
#define TCP_MAX_SOCKETS 1024
@@ -43,7 +46,6 @@ struct tcp_listener_t {
tcp_connection_t *non_established_list;
tcp_connection_t *non_accepted_queue_head;
tcp_connection_t *non_accepted_queue_tail;
void (*callback_ready_to_accept)(void*);
void *callback_data;
};