From 04ce79bcf07922655214568415e3b579a2355002 Mon Sep 17 00:00:00 2001 From: cozis Date: Sun, 21 May 2023 21:56:21 +0200 Subject: [PATCH] new TCP timer object; solved a bunch of warnings; completely implemented the TCP termination sequence --- makefile | 4 ++ src/icmp.c | 2 +- src/ip.c | 2 +- src/microtcp.c | 30 ++++++-- src/tcp.c | 188 ++++++++++++++++++++++++++++++++---------------- src/tcp.h | 25 +++++-- src/tcp_timer.c | 141 ++++++++++++++++++++++++++++++++++++ src/tcp_timer.h | 29 ++++++++ 8 files changed, 345 insertions(+), 76 deletions(-) create mode 100644 src/tcp_timer.c create mode 100644 src/tcp_timer.h diff --git a/makefile b/makefile index f7f7220..197ee4c 100644 --- a/makefile +++ b/makefile @@ -117,6 +117,8 @@ build/microtcp.c: 3p/include/tinycthread.h 3p/src/tinycthread.c $(wildcard src/* cat src/icmp.h >> $@ printf "\n#line 1 \"src/ip.h\"\n" >> $@ cat src/ip.h >> $@ + printf "\n#line 1 \"src/tcp_timer.h\"\n" >> $@ + cat src/tcp_timer.h >> $@ printf "\n#line 1 \"src/tcp.h\"\n" >> $@ cat src/tcp.h >> $@ printf "\n#line 1 \"src/endian.c\"\n" >> $@ @@ -127,6 +129,8 @@ build/microtcp.c: 3p/include/tinycthread.h 3p/src/tinycthread.c $(wildcard src/* cat src/icmp.c >> $@ printf "\n#line 1 \"src/ip.c\"\n" >> $@ cat src/ip.c >> $@ + printf "\n#line 1 \"src/tcp_timer.c\"\n" >> $@ + cat src/tcp_timer.c >> $@ printf "\n#line 1 \"src/tcp.c\"\n" >> $@ cat src/tcp.c >> $@ printf "\n#line 1 \"src/microtcp.c\"\n" >> $@ diff --git a/src/icmp.c b/src/icmp.c index ebe7794..b476744 100644 --- a/src/icmp.c +++ b/src/icmp.c @@ -89,7 +89,7 @@ void icmp_process_packet(icmp_state_t *state, ip_address_t ip, const void *src, } 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); + ICMP_DEBUG_LOG("Ignoring ECHO REQUEST because the output buffer is too small for an ECHO REPLY (have %d, need %d)", (int) state->output_len, (int) len); return; } diff --git a/src/ip.c b/src/ip.c index d4306a0..a548b08 100644 --- a/src/ip.c +++ b/src/ip.c @@ -239,7 +239,7 @@ void ip_process_packet(ip_state_t *ip_state, const void *packet, size_t len) size_t option_count = header_length - sizeof(ip_packet_t)/4; if (option_count > 0) { - #warning "TODO: Handle IP options" + // TODO: Handle IP options return; } diff --git a/src/microtcp.c b/src/microtcp.c index d7d915e..3e2b615 100644 --- a/src/microtcp.c +++ b/src/microtcp.c @@ -187,7 +187,6 @@ static void send_arp_packet(void *data, mac_address_t dst) frame->proto = cpu_to_net_u16(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) @@ -828,9 +827,10 @@ microtcp_socket_t *microtcp_open(microtcp_t *mtcp, uint16_t port, goto unlock_and_exit; // Socket limit reached } - tcp_listener_t *listener = tcp_listener_create(&mtcp->tcp_state, port, socket, ready_to_accept); + tcp_listener_t *listener = tcp_listener_create(&mtcp->tcp_state, port, socket, false, ready_to_accept); if (listener == NULL) { - #warning "This error code should be more specific, but the TCP module isn't stable yet" + // 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; @@ -870,10 +870,22 @@ void microtcp_close(microtcp_socket_t *socket) microtcp_t *mtcp = socket->mtcp; - #warning "sockets should unregister from all multiplexers" - 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: @@ -1038,7 +1050,10 @@ size_t microtcp_recv(microtcp_socket_t *socket, errcode2 = MICROTCP_ERRCODE_CANTBLOCK; } } -unlock_and_exit: + + goto unlock_and_exit; // Warning + unlock_and_exit: + UNLOCK_WHEN_THREADED(mtcp); if (errcode) @@ -1082,7 +1097,8 @@ size_t microtcp_send(microtcp_socket_t *socket, errcode2 = MICROTCP_ERRCODE_CANTBLOCK; } } -unlock_and_exit: + goto unlock_and_exit; // Warning + unlock_and_exit: UNLOCK_WHEN_THREADED(mtcp); if (errcode) diff --git a/src/tcp.c b/src/tcp.c index 9ea50ae..d7e47d9 100644 --- a/src/tcp.c +++ b/src/tcp.c @@ -31,19 +31,20 @@ void tcp_init(tcp_state_t *tcp_state, ip_address_t ip, tcp_callbacks_t callbacks 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; + + tcp_timerset_init(&tcp_state->timers); } -void tcp_free(tcp_state_t *tcp_state) +void tcp_free(tcp_state_t *state) { - // Destroy all listening connections - while (tcp_state->used_listener_list != NULL) - tcp_listener_destroy(tcp_state->used_listener_list); + // It's not clear to me how to free + // all of this stuff up at the moment :/ + tcp_timerset_free(&state->timers); } void tcp_seconds_passed(tcp_state_t *state, size_t seconds) { - (void) state; - (void) seconds; + tcp_timerset_step(&state->timers, seconds); } static tcp_connection_t* @@ -155,7 +156,7 @@ calculate_checksum(const slice_list_t *slices, size_t num_slices) } static void emit_segment(tcp_connection_t *connection, uint8_t flags, size_t payload) -{ +{ tcp_listener_t *listener = connection->listener; tcp_state_t *state = listener->state; @@ -337,22 +338,79 @@ static bool ack_until(tcp_connection_t *connection, uint32_t ack_no) return true; } +static void +really_close_listener(tcp_listener_t *listener) +{ + 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; +} + +static bool listener_has_no_connections(const tcp_listener_t *listener) +{ + return listener->accepted_list == NULL + && listener->non_established_list == NULL + && listener->non_accepted_queue_head == NULL; +} + static void really_close_connection(tcp_connection_t *connection) { tcp_listener_t *listener = connection->listener; tcp_state_t *state = listener->state; - // Pop connection from the accepted_list + // Pop connection from the list it's in + tcp_connection_t *next = connection->next; if (connection->prev) - connection->prev->next = connection->next; - else - listener->accepted_list = connection->next; + connection->prev->next = next; + else if (listener->accepted_list == connection) + listener->accepted_list = next; + else if (listener->non_established_list == connection) + listener->non_established_list = next; + else if (listener->non_accepted_queue_head == connection) + listener->non_accepted_queue_head = next; + tcp_connection_t *prev = connection->prev; + if (next) + connection->next->prev = prev; + else if (listener->non_accepted_queue_tail == connection) + listener->non_accepted_queue_tail = prev; // Push it into the free connection list connection->prev = NULL; connection->next = state->free_connection_list; state->free_connection_list = connection; + + // If the listener is waiting to be closed + // and this was the last connection it was + // holding, then free the listener + if (listener->closed == true && listener_has_no_connections(listener)) + really_close_listener(listener); +} + +static void timeout_callback_time_wait(void *data) +{ + tcp_connection_t *connection = data; + assert(connection->state == TCP_STATE_TIME_WAIT); + + // We can finally free up this connection structure! + really_close_connection(connection); } void tcp_process_segment(tcp_state_t *state, ip_address_t sender, @@ -388,6 +446,13 @@ void tcp_process_segment(tcp_state_t *state, ip_address_t sender, tcp_connection_t *connection = find_connection_associated_to_listener(listener, sender, reordered_src_port); if (!connection) { + if (listener->closed) { + // Listener was closed by the user, so already + // open connections are ok but new ones are not + // allowed. + return; + } + TCP_DEBUG_LOG("Connection request from port %d", reordered_src_port); // Something sent to an open listener. @@ -534,10 +599,17 @@ void tcp_process_segment(tcp_state_t *state, ip_address_t sender, if (segment->flags & (TCP_FLAG_FIN | TCP_FLAG_ACK)) { TCP_DEBUG_LOG("FIN-WAIT-1 -> TIME-WAIT"); + connection->rcv_nxt++; // FIN ghost byte emit_segment(connection, TCP_FLAG_ACK, 0); + connection->state = TCP_STATE_TIME_WAIT; + // Don't close the connection just now but + // wait for a given amount of time first. + if (!tcp_timer_create(&state->timers, TCP_TIMEOUT_TIME_WAIT, timeout_callback_time_wait, connection)) + assert(0); + } else if (segment->flags & TCP_FLAG_ACK) { TCP_DEBUG_LOG("FIN-WAIT-1 -> FIN-WAIT-2"); @@ -567,6 +639,11 @@ void tcp_process_segment(tcp_state_t *state, ip_address_t sender, emit_segment(connection, TCP_FLAG_ACK, 0); connection->state = TCP_STATE_TIME_WAIT; + + // Don't close the connection just now but + // wait for a given amount of time first. + if (!tcp_timer_create(&state->timers, TCP_TIMEOUT_TIME_WAIT, timeout_callback_time_wait, connection)) + assert(0); break; case TCP_STATE_CLOSE_WAIT: @@ -609,70 +686,55 @@ void tcp_process_segment(tcp_state_t *state, ip_address_t sender, } tcp_listener_t* -tcp_listener_create(tcp_state_t *state, uint16_t port, void *callback_data, - void (*callback_ready_to_accept)(void*)) +tcp_listener_create(tcp_state_t *state, uint16_t port, bool reuse, + void *callback_data, void (*callback_ready_to_accept)(void*)) { - if (find_listener_with_port(state, port)) { + tcp_listener_t *listener; + + listener = find_listener_with_port(state, port); + if (listener && (!listener->closed || !reuse)) { // 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; + if (listener) + listener->closed = false; + else { + + // 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; + } + listener = state->free_listener_list; + state->free_listener_list = listener->next; + + // Initialize listener structure + listener->state = state; + listener->closed = false; + listener->port = port; + listener->accepted_list = NULL; + listener->non_established_list = NULL; + listener->non_accepted_queue_head = NULL; + listener->non_accepted_queue_tail = NULL; + listener->callback_data = callback_data; + listener->callback_ready_to_accept = callback_ready_to_accept; + + // 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; } - tcp_listener_t *listener = state->free_listener_list; - state->free_listener_list = listener->next; - - // Initialize listener structure - listener->state = state; - listener->port = port; - listener->accepted_list = NULL; - listener->non_established_list = NULL; - listener->non_accepted_queue_head = NULL; - listener->non_accepted_queue_tail = NULL; - listener->callback_data = callback_data; - listener->callback_ready_to_accept = callback_ready_to_accept; - - // 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) { - #warning "The previously accepted connections should't be closed with their listener.. I think" - - // 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; + listener->closed = true; } tcp_connection_t *tcp_listener_accept(tcp_listener_t *listener, void *callback_data, diff --git a/src/tcp.h b/src/tcp.h index abf2945..68c7a2e 100644 --- a/src/tcp.h +++ b/src/tcp.h @@ -3,8 +3,12 @@ #ifndef MICROTCP_AMALGAMATION #include "defs.h" +#include "tcp_timer.h" #endif +#define TCP_TIMEOUT_TIME_WAIT 240 + +#define TCP_MAX_TIMEOUTS 1024 #define TCP_MAX_LISTENERS 32 #define TCP_MAX_SOCKETS 1024 #define TCP_INPUT_BUFFER_SIZE 1024 @@ -35,12 +39,19 @@ typedef struct { static_assert(sizeof(tcp_segment_t) == 20); typedef struct tcp_connection_t tcp_connection_t; -typedef struct tcp_listener_t tcp_listener_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; + bool closed; // When a listener is closed while one or more connections that is + // previously accepted are open, the structure isn't deallocated + // but just marked lazily as "closed". A listener in the "closed-but-not-deallocated" + // state will not accept new connections but will serve the ones + // its still holding. + // In this state the listener can be reopened by setting the "closed" + // flag to true (and keeping the old connections intact). uint16_t port; tcp_connection_t *accepted_list; tcp_connection_t *non_established_list; @@ -138,20 +149,26 @@ typedef struct { } tcp_callbacks_t; struct tcp_state_t { + ip_address_t ip; tcp_callbacks_t callbacks; + + tcp_timerset_t timers; + 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]; + + tcp_listener_t listener_pool[TCP_MAX_LISTENERS]; + tcp_connection_t connection_pool[TCP_MAX_SOCKETS]; }; void tcp_init(tcp_state_t *tcp_state, ip_address_t ip, 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*)); +tcp_listener_t *tcp_listener_create(tcp_state_t *state, uint16_t port, bool reuse, void *data, void (*callback)(void*)); void tcp_listener_destroy(tcp_listener_t *listener); tcp_connection_t *tcp_listener_accept(tcp_listener_t *listener, void *callback_data, void (*callback_ready_to_recv)(void*), void (*callback_ready_to_send)(void*)); void tcp_connection_destroy(tcp_connection_t *connection); diff --git a/src/tcp_timer.c b/src/tcp_timer.c new file mode 100644 index 0000000..16a8ea9 --- /dev/null +++ b/src/tcp_timer.c @@ -0,0 +1,141 @@ +#ifndef MICROTCP_AMALGAMATION +#include "tcp_timer.h" +#endif + +void tcp_timerset_init(tcp_timerset_t *set) +{ + for (size_t i = 0; i < TCP_MAX_TIMEOUTS-1; i++) + set->pool[i].next = set->pool + i+1; + set->pool[TCP_MAX_TIMEOUTS-1].next = NULL; + set->free_list = set->pool; + set->used_list = NULL; +} + +void tcp_timerset_free(tcp_timerset_t *set) +{ + (void) set; +} + +void tcp_timer_disable(tcp_timer_t *timer) +{ + tcp_timerset_t *set = timer->set; + + // Pop the timer from the used list + if (timer->prev) + timer->prev->next = timer->next; + else + set->used_list = timer->next; + if (timer->next) + timer->next->prev = timer->prev; + + // Push it into the free list + timer->prev = NULL; + timer->next = set->free_list; + set->free_list = timer; +} + +tcp_timer_t *tcp_timer_create(tcp_timerset_t *set, size_t seconds, + void (*callback)(void*), void *data) +{ + assert(callback); + + if (set->free_list == NULL) + // Out of timers! This is really bad. + // What can be done to mitigate this? + return NULL; + + tcp_timer_t *timer = set->free_list; + set->free_list = timer->next; + // NOTE: Since the free list is singly linked, there's + // no need to change the prev member of the new + // first item of the free list. + + timer->set = set; + timer->deadline = set->current_time + seconds; + timer->data = data; + timer->callback = callback; + + // Insert the timer structure into the timer list + // in an orderly fashon + if (set->used_list == NULL) { + // This is the first timer of the list + set->used_list = timer; + timer->prev = NULL; + timer->next = NULL; + } else if (timer->deadline < set->used_list->deadline) { + // This timer should be the first of the list + timer->prev = NULL; + timer->next = set->used_list; + set->used_list->prev = timer; + } else { + // The timer isn't the first of the list. We need to + // determine at which position it should be inserted + // + // Scan the list until the timer that needs to come + // after the inserted one is reached + tcp_timer_t *cursor = set->used_list; + while (cursor->next && cursor->next->deadline <= timer->deadline) + cursor = cursor->next; + tcp_timer_t *prev = cursor; + cursor = cursor->next; + + if (cursor) { + // The cursor points to the element that needs to + // come after. Since we know the inserted item won't + // be the first, then this one isn't the first element + // of the list either, so its "prev" isn't NULL. + assert(cursor->prev); + timer->prev = cursor->prev; + timer->next = cursor; + cursor->prev->next = timer; + cursor->prev = timer; + } else { + // No element that needs to come after was found, + // so its position should be the last. + prev->next = timer; + timer->prev = prev; + timer->next = NULL; + } + } + return timer; +} + +void tcp_timerset_step(tcp_timerset_t *set, size_t seconds) +{ + set->current_time += seconds; + + if (set->used_list == NULL || set->used_list->deadline > set->current_time) + // No timeouts triggered + return; + + tcp_timer_t *timedout_head = set->used_list; // We know that at least one timeout triggered + tcp_timer_t *timedout_tail; // This has to be determined by the following loop + + // Scan through all of the timeouts that just triggered + tcp_timer_t *timeout = set->used_list; + while (timeout) { + + if (timeout->deadline > set->current_time) + // This timeout didn't trigger, so the last + // timed out timeout was the previous one. + break; + + // Trigger the callback + timeout->callback(timeout->data); + + timedout_tail = timeout; + timeout = timeout->next; + } + + // Now put the list of timed out timeouts back + // into the free list + if (timedout_tail->next) + timedout_tail->next->prev = NULL; + + set->used_list = timedout_tail->next; + + timedout_tail->prev = NULL; + timedout_tail->next = set->free_list; + + set->free_list = timedout_head; +} \ No newline at end of file diff --git a/src/tcp_timer.h b/src/tcp_timer.h new file mode 100644 index 0000000..43db4b6 --- /dev/null +++ b/src/tcp_timer.h @@ -0,0 +1,29 @@ +#include + +#define TCP_MAX_TIMERS 1024 + +typedef struct tcp_timerset_t tcp_timerset_t; +typedef struct tcp_timer_t tcp_timer_t; + +struct tcp_timer_t { + tcp_timerset_t *set; + tcp_timer_t *prev; + tcp_timer_t *next; + uint64_t deadline; + void (*callback)(void *data); + void *data; +}; + +struct tcp_timerset_t { + uint64_t current_time; + tcp_timer_t *used_list; + tcp_timer_t *free_list; + tcp_timer_t pool[TCP_MAX_TIMERS]; +}; + +void tcp_timerset_step(tcp_timerset_t *set, size_t seconds); +void tcp_timerset_init(tcp_timerset_t *set); +void tcp_timerset_free(tcp_timerset_t *set); +void tcp_timer_disable(tcp_timer_t *timer); +tcp_timer_t *tcp_timer_create(tcp_timerset_t *set, size_t seconds, + void (*callback)(void*), void *data);