From 733597b6aa39eb25a64f0fc9deddf6075037a946 Mon Sep 17 00:00:00 2001 From: cozis Date: Sun, 29 Oct 2023 22:38:14 +0100 Subject: [PATCH] general cleanups --- .gitmodules | 3 + 3p/packetdrill | 1 + examples/echo_tcp.c | 51 +- examples/echo_tcp_mux.c | 25 + makefile | 11 +- src/arp.c | 12 +- src/defs.h | 6 + src/icmp.c | 4 +- src/ip.h | 2 +- src/microtcp.c | 89 +- src/microtcp.h | 3 + src/tcp.c | 2098 +++++++++++++++++++++++++-------------- src/tcp.h | 99 +- src/tcp_timer.c | 8 +- src/tcp_timer.h | 2 + src/utils.c | 28 +- 16 files changed, 1525 insertions(+), 917 deletions(-) create mode 160000 3p/packetdrill create mode 100644 examples/echo_tcp_mux.c diff --git a/.gitmodules b/.gitmodules index 8e15dbc..e5a69b7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "3p/tinycthread"] path = 3p/tinycthread url = https://github.com/tinycthread/tinycthread.git +[submodule "3p/packetdrill"] + path = 3p/packetdrill + url = https://github.com/google/packetdrill.git diff --git a/3p/packetdrill b/3p/packetdrill new file mode 160000 index 0000000..8595c94 --- /dev/null +++ b/3p/packetdrill @@ -0,0 +1 @@ +Subproject commit 8595c94c9cbb46cb4828ab3db0c71ad5c9b854af diff --git a/examples/echo_tcp.c b/examples/echo_tcp.c index efa79a0..39c43ef 100644 --- a/examples/echo_tcp.c +++ b/examples/echo_tcp.c @@ -3,58 +3,17 @@ int main(void) { - microtcp_errcode_t errcode; - microtcp_t *mtcp = microtcp_create("10.0.0.5", "10.0.0.4", NULL, NULL); - if (mtcp == NULL) { - fprintf(stderr, "Error: Failed to instanciate microtcp stack\n"); - return -1; - } uint16_t port = 8081; - microtcp_socket_t *server = microtcp_open(mtcp, port, &errcode); - if (errcode) { - fprintf(stderr, "Error: %s\n", microtcp_strerror(errcode)); - microtcp_destroy(mtcp); - return -1; - } - assert(server); - - fprintf(stderr, "Listening on port %d\n", port); + microtcp_socket_t *server = microtcp_open(mtcp, port, NULL); while (1) { - fprintf(stderr, "About to accept\n"); - microtcp_socket_t *client = microtcp_accept(server, false, &errcode); - if (errcode) { - fprintf(stderr, "Error: %s\n", microtcp_strerror(errcode)); - break; - } - - fprintf(stderr, "Accepted a connection\n"); - + microtcp_socket_t *client = microtcp_accept(server, false, NULL); char buffer[1024]; - size_t num = microtcp_recv(client, buffer, sizeof(buffer), false, &errcode); - if (errcode) { - fprintf(stderr, "Error: %s\n", microtcp_strerror(errcode)); - goto handled; - } - fprintf(stderr, "(%d bytes received)\n", (int) num); - - size_t sent1 = microtcp_send(client, "echo: ", 6, false, &errcode); - if (errcode) { - fprintf(stderr, "Error: %s\n", microtcp_strerror(errcode)); - goto handled; - } - fprintf(stderr, "(%d bytes sent 1)\n", (int) sent1); - - size_t sent2 = microtcp_send(client, buffer, num, false, &errcode); - if (errcode) { - fprintf(stderr, "Error: %s\n", microtcp_strerror(errcode)); - goto handled; - } - fprintf(stderr, "(%d bytes sent 2)\n", (int) sent2); - -handled: + size_t num = microtcp_recv(client, buffer, sizeof(buffer), false, NULL); + microtcp_send(client, "echo: ", 6, false, NULL); + microtcp_send(client, buffer, num, false, NULL); microtcp_close(client); } diff --git a/examples/echo_tcp_mux.c b/examples/echo_tcp_mux.c new file mode 100644 index 0000000..7bd3811 --- /dev/null +++ b/examples/echo_tcp_mux.c @@ -0,0 +1,25 @@ + + +int main(void) +{ + microtcp_t *mtcp = microtcp_create(..); + + uint16_t port = 8080; + microtcp_socket_t *socket = microtcp_open(mtcp, port, 0); + + microtcp_mux_t *mux = microtcp_mux_create(mtcp); + for (int i = 0; i < 3; i++) { + microtcp_socket_t *accepted = microtcp_accept(socket, 0, 0); + microtcp_mux_register(mux, accepted, MICROTCP_MUX_RECV | MICROTCP_MUX_SEND); + } + + for (microtcp_muxevent_t event; microtcp_mux_wait(mux, &event)) { + if (event.events & MICROTCP_MUX_RECV) { + // Il socket "event.socket" ha dei dati da leggere + } else { + // Il socket "event.socket" ha spazio per inviare + } + } + + microtcp_destroy(mtcp); +} \ No newline at end of file diff --git a/makefile b/makefile index a5925f7..eb5687d 100644 --- a/makefile +++ b/makefile @@ -48,13 +48,13 @@ MEMDBG=valgrind LIBDIR = 3p/lib INCDIR = 3p/include -CFLAGS = $(CFLAGS_PLATFORM) -I$(INCDIR) -Ibuild/ -Wall -Wextra -LFLAGS = -ltuntap $(LFLAGS_PLATFORM) -L$(LIBDIR) +CFLAGS = $(CUSTOM_CFLAGS) $(CFLAGS_PLATFORM) -I$(INCDIR) -Ibuild/ -Wall -Wextra +LFLAGS = $(CUSTOM_LFLAGS) -ltuntap $(LFLAGS_PLATFORM) -L$(LIBDIR) ifeq ($(MEMDBG),drmemory) CFLAGS += -gdwarf-2 else - CFLAGS += -g + CFLAGS += -g3 endif .PHONY: all clean @@ -154,7 +154,4 @@ report: build/test gcov -b build/test-tcp_timer.c clean: - rm build/*.gcda build/*.gcno - rm -fr build - rm -fr 3p/libtuntap/build - rm -f 3p/lib/* 3p/include/* \ No newline at end of file + rm -fr build 3p/libtuntap/build 3p/lib/* 3p/include/* \ No newline at end of file diff --git a/src/arp.c b/src/arp.c index c8ad9a3..8cc0201 100644 --- a/src/arp.c +++ b/src/arp.c @@ -690,17 +690,7 @@ arp_process_result_t arp_process_packet(arp_state_t *state, const void *packet, ARP_DEBUG_LOG("Invalid hardware or protocol address size %d or %d (expected %d and %d)", packet2->hardware_len, packet2->protocol_len, 6, 4); return ARP_PROCESS_RESULT_INVALID; } -/* - ARP_DEBUG_LOG("ARP from %d.%d.%d.%d to %d.%d.%d.%d", - ((uint8_t*) &packet2->sender_protocol_address)[0], - ((uint8_t*) &packet2->sender_protocol_address)[1], - ((uint8_t*) &packet2->sender_protocol_address)[2], - ((uint8_t*) &packet2->sender_protocol_address)[3], - ((uint8_t*) &packet2->target_protocol_address)[0], - ((uint8_t*) &packet2->target_protocol_address)[1], - ((uint8_t*) &packet2->target_protocol_address)[2], - ((uint8_t*) &packet2->target_protocol_address)[3]); -*/ + bool merge = arp_translation_table_update(&state->table, packet2->sender_hardware_address, packet2->sender_protocol_address, state->cache_timeout); diff --git a/src/defs.h b/src/defs.h index f063bd7..89d3ac7 100644 --- a/src/defs.h +++ b/src/defs.h @@ -26,4 +26,10 @@ typedef struct { #define COUNT(X) ((int) (sizeof(X) / sizeof((X)[0]))) #define SLICE(X) ((slice_t) {.ptr=&(X), .len=sizeof(X)}) +#define UNPACK_IP(IP) \ + ((IP) >> 0 & 0xff), \ + ((IP) >> 8 & 0xff), \ + ((IP) >> 16 & 0xff), \ + ((IP) >> 24 & 0xff) + #endif /* MICROTCP_DEFS_H */ \ No newline at end of file diff --git a/src/icmp.c b/src/icmp.c index b476744..6af58ee 100644 --- a/src/icmp.c +++ b/src/icmp.c @@ -89,7 +89,9 @@ 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 %d, need %d)", (int) state->output_len, (int) 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.h b/src/ip.h index 326738a..ffa3e21 100644 --- a/src/ip.h +++ b/src/ip.h @@ -22,7 +22,7 @@ typedef struct { uint32_t src_ip; uint32_t dst_ip; char payload[]; -} ip_packet_t; +} __attribute__((packed)) ip_packet_t; static_assert(sizeof(ip_packet_t) == 20); typedef enum { diff --git a/src/microtcp.c b/src/microtcp.c index 7b1cb50..8667cf4 100644 --- a/src/microtcp.c +++ b/src/microtcp.c @@ -67,10 +67,10 @@ struct microtcp_mux_t { typedef struct buffer_t buffer_t; struct buffer_t { microtcp_t *mtcp; - buffer_t *prev; - buffer_t *next; - size_t used; - char data[1518]; + buffer_t *prev; + buffer_t *next; + size_t used; + char data[1218]; }; typedef enum { @@ -143,6 +143,7 @@ const char *microtcp_strerror(microtcp_errcode_t errcode) 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"; + case MICROTCP_ERRCODE_PEERCLOSED: return "Peer closed the connection"; } return "???"; } @@ -374,7 +375,8 @@ process_packet(microtcp_t *mtcp, const void *packet, size_t len) const ethernet_frame_t *frame = packet; switch (net_to_cpu_u16(frame->proto)) { - case ETHERNET_PROTOCOL_ARP: + + case ETHERNET_PROTOCOL_ARP: arp_process_packet(&mtcp->arp_state, frame+1, len - sizeof(ethernet_frame_t)); break; @@ -644,7 +646,8 @@ 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; + if (socket) + mtcp->free_socket_list = socket->next; return socket; } @@ -689,18 +692,24 @@ static void signal_events_to_muxes_associated_to_socket(microtcp_socket_t *socket, int events); #endif -static void ready_to_accept(void *data) +static void listener_event_callback(void *data, tcp_listenevent_t event) { microtcp_socket_t *socket = data; (void) socket; #ifdef MICROTCP_BACKGROUND_THREAD - cnd_signal(&socket->something_to_accept); + switch (event) { + case TCP_LISTENEVENT_ACCEPT: cnd_signal(&socket->something_to_accept); break; + } #endif #ifdef MICROTCP_USING_MUX - MICROTCP_DEBUG_LOG("Signaling ACCEPT to muxes"); - signal_events_to_muxes_associated_to_socket(socket, MICROTCP_MUX_ACCEPT); + int flags = 0; + switch (event) { + case TCP_LISTENEVENT_ACCEPT: flags = MICROTCP_MUX_ACCEPT; MICROTCP_DEBUG_LOG("Signaling ACCEPT to muxes"); break; + } + if (flags) + signal_events_to_muxes_associated_to_socket(socket, flags); #endif } @@ -717,7 +726,7 @@ 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, false, socket, ready_to_accept); + tcp_listener_t *listener = tcp_listener_create(&mtcp->tcp_state, port, false, socket, listener_event_callback); if (listener == NULL) { // FIXME: This error code should be more specific, // but the TCP module isn't stable yet @@ -786,7 +795,9 @@ void microtcp_close(microtcp_socket_t *socket) break; case SOCKET_CONNECTION: - tcp_connection_destroy(socket->connection); + if (socket->connection) // Only need to close the connection + // if the peer didn't already. + tcp_connection_destroy(socket->connection); break; } @@ -796,33 +807,34 @@ void microtcp_close(microtcp_socket_t *socket) UNLOCK_WHEN_THREADED(mtcp); } -static void ready_to_recv(void *data) +static void conn_event_callback(void *data, tcp_connevent_t event) { microtcp_socket_t *socket = data; (void) socket; #ifdef MICROTCP_BACKGROUND_THREAD - cnd_signal(&socket->something_to_recv); + switch (event) { + + case TCP_CONNEVENT_RECV: MICROTCP_DEBUG_LOG("Signal RECV"); cnd_signal(&socket->something_to_recv); break; + case TCP_CONNEVENT_SEND: MICROTCP_DEBUG_LOG("Signal SEND"); cnd_signal(&socket->something_to_send); break; + + case TCP_CONNEVENT_RESET: + case TCP_CONNEVENT_CLOSE: + socket->connection = NULL; + break; + } #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); + // TODO: Maybe signal closing and reset events to muxes? + int flags; + switch (event) { + case TCP_CONNEVENT_RECV: flags = MICROTCP_MUX_RECV; MICROTCP_DEBUG_LOG("Signaling RECV to muxes"); break; + case TCP_CONNEVENT_SEND: flags = MICROTCP_MUX_SEND; MICROTCP_DEBUG_LOG("Signaling SEND to muxes"); break; + default: flags = 0; break; + } + if (flags) + signal_events_to_muxes_associated_to_socket(socket, flags); #endif } @@ -847,7 +859,7 @@ microtcp_socket_t *microtcp_accept(microtcp_socket_t *socket, goto unlock_and_exit; // Socket limit reached } - tcp_connection_t *connection = tcp_listener_accept(socket->listener, socket2, ready_to_recv, ready_to_send); + tcp_connection_t *connection = tcp_listener_accept(socket->listener, socket2, conn_event_callback); #ifdef MICROTCP_BACKGROUND_THREAD while (!connection && !no_block) { @@ -856,7 +868,7 @@ microtcp_socket_t *microtcp_accept(microtcp_socket_t *socket, 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); + connection = tcp_listener_accept(socket->listener, socket2, conn_event_callback); } #else if (!connection) { @@ -916,6 +928,12 @@ size_t microtcp_recv(microtcp_socket_t *socket, return 0; } + if (socket->connection == NULL) { + if (errcode) + *errcode = MICROTCP_ERRCODE_PEERCLOSED; + return 0; + } + size_t num; microtcp_t *mtcp = socket->mtcp; microtcp_errcode_t errcode2 = MICROTCP_ERRCODE_NONE; @@ -962,6 +980,11 @@ size_t microtcp_send(microtcp_socket_t *socket, return 0; } + if (socket->connection == NULL) { + if (errcode) + *errcode = MICROTCP_ERRCODE_PEERCLOSED; + return 0; + } size_t num; microtcp_t *mtcp = socket->mtcp; diff --git a/src/microtcp.h b/src/microtcp.h index 7f745cb..8872db7 100644 --- a/src/microtcp.h +++ b/src/microtcp.h @@ -32,6 +32,9 @@ typedef enum { // Returned by microtcp_accept, microtcp_recv and microtcp_send MICROTCP_ERRCODE_WOULDBLOCK, + // Returned by microtcp_recv, microtcp_send + MICROTCP_ERRCODE_PEERCLOSED, + } microtcp_errcode_t; typedef struct { diff --git a/src/tcp.c b/src/tcp.c index 4d4d42e..d16bde3 100644 --- a/src/tcp.c +++ b/src/tcp.c @@ -11,9 +11,11 @@ # include # define TCP_DEBUG_LOG(fmt, ...) fprintf(stderr, "TCP :: " fmt "\n", ## __VA_ARGS__) #else -# define TCP_DEBUG_LOG(...) +# define TCP_DEBUG_LOG(...) {} #endif +#define SLICE_EMPTY ((slice_t) {.ptr=NULL, .len=0}) + #define SEGMENT_OFFSET(seg) (cpu_is_little_endian() ? (seg)->offset2 : (seg)->offset1) void tcp_init(tcp_state_t *tcp_state, ip_address_t ip, tcp_callbacks_t callbacks) @@ -48,86 +50,64 @@ void tcp_ms_passed(tcp_state_t *state, size_t ms) } static tcp_connection_t* -connection_create(tcp_listener_t *listener, uint32_t seq_no, - uint32_t ack_no, ip_address_t peer_ip, uint16_t peer_port) +get_conn_struct(tcp_state_t *tcp) +{ + tcp_connection_t *c = tcp->free_connection_list; + if (c) { + tcp->free_connection_list = c->next; + c->prev = NULL; + c->next = NULL; + } + return c; +} + +static void +append_new_conn_to_listener(tcp_connection_t *c, + tcp_listener_t *listener) +{ + if (listener->noestab) + listener->noestab->prev = c; + c->next = listener->noestab; + listener->noestab = c; + c->listener = listener; + listener->count++; +} + +static tcp_connection_t* +connection_create(tcp_listener_t *listener, ip_address_t ip, uint16_t port, uint32_t seq, uint32_t ack) { tcp_state_t *state = listener->state; // Pop a connection structure from the free list - tcp_connection_t *connection; - { - if (state->free_connection_list == NULL) - // ERROR: Reached connection limit - return NULL; - connection = state->free_connection_list; - state->free_connection_list = connection->next; - } + tcp_connection_t *c = get_conn_struct(state); + if (c == NULL) + // ERROR: Reached connection limit + return NULL; - // Initialize connection structure - { - connection->listener = listener; - - connection->callback_data = NULL; - connection->callback_ready_to_recv = NULL; - connection->callback_ready_to_send = NULL; + c->listener = NULL; + c->cb_data = NULL; + c->cb_event = NULL; + c->state = TCP_STATE_CLOSED; + c->peer_port = port; + c->peer_ip = ip; + c->retr_timer = NULL; + c->wait_timer = NULL; + c->rcv_unread = ack; + c->rcv_nxt = ack; + c->rcv_wnd = TCP_IBUFFER_SIZE; + c->snd_una = seq; + c->snd_wnd = 0; + c->snd_nxt = seq; + c->snd_wl1 = seq; + c->snd_wl2 = ack; + c->last_acked = 0; + c->oused = 0; + c->waiting_ack_for_syn = false; + c->waiting_ack_for_fin = false; + c->send_fin_when_fully_flushed = false; - connection->state = TCP_STATE_CLOSED; - - connection->estimated_rtt = 0; - connection->estimated_dev = 0; - - connection->calculating_rtt = false; - connection->rtt_calc_seq = 0; - connection->rtt_calc_time = 0; - - connection->retr_timer = NULL; - - connection->peer_port = peer_port; - connection->peer_ip = peer_ip; - - connection->rcv_unread = ack_no; - connection->rcv_nxt = ack_no; - connection->rcv_wnd = TCP_INPUT_BUFFER_SIZE; - - connection->snd_una = seq_no; - connection->snd_wnd = 0; - connection->snd_nxt = seq_no; - - connection->in_buffer_syn = false; - connection->in_buffer_fin = false; - - connection->prev = NULL; - connection->next = NULL; - } - - // Appent to the list of not yet established connections - if (listener->non_established_list) - listener->non_established_list->prev = connection; - connection->next = listener->non_established_list; - listener->non_established_list = connection; - - return connection; -} - -static void -signal_ready_to_send(tcp_connection_t *c) -{ - if (c->callback_ready_to_send) - c->callback_ready_to_send(c->callback_data); -} - -static void -signal_ready_to_recv(tcp_connection_t *c) -{ - if (c->callback_ready_to_recv) - c->callback_ready_to_recv(c->callback_data); -} - -static void -signal_ready_to_accept(tcp_listener_t *l) -{ - if (l->callback_ready_to_accept) - l->callback_ready_to_accept(l->callback_data); + append_new_conn_to_listener(c, listener); + return c; } static tcp_listener_t* @@ -147,6 +127,42 @@ static uint32_t choose_sequence_no(void) return 0; } +static void retransmit(tcp_connection_t *c); + +static void retr_timeout_callback(void *data) +{ + tcp_connection_t *c = data; + tcp_listener_t *l = c->listener; + tcp_state_t *tcp = l->state; + + retransmit(c); + + // Start a new retransmission timer + size_t ms = 1000; + c->retr_timer = tcp_timer_create(&tcp->timers, ms, "retr", retr_timeout_callback, c); + if (c->retr_timer == NULL) + TCP_DEBUG_LOG("Couldn't set retransmission timer"); +} + +static void really_close_connection(tcp_connection_t *c); + +static void wait_timeout_callback(void *data) +{ + tcp_connection_t *c = data; + really_close_connection(c); +} + +typedef struct { + ip_address_t self; + ip_address_t peer; + uint16_t src_port; + uint16_t dst_port; + uint32_t seq, ack; + uint8_t flags; + uint16_t window; + slice_t payload; +} transmit_config_t; + typedef struct { ip_address_t src_addr; ip_address_t dst_addr; @@ -156,7 +172,7 @@ typedef struct { } tcp_pseudoheader_t; // Ensure packed? static uint16_t -calculate_byte_checksum(const slice_t *slices, size_t num_slices) +calc_checksum(const slice_t *slices, size_t num_slices) { uint32_t sum = 0xffff; @@ -170,7 +186,7 @@ calculate_byte_checksum(const slice_t *slices, size_t num_slices) if (sum > 0xffff) sum -= 0xffff; } - + if (len & 1) { alignas(uint16_t) uint8_t temp[2]; @@ -187,263 +203,334 @@ calculate_byte_checksum(const slice_t *slices, size_t num_slices) return cpu_to_net_u16(~sum); } +static tcp_pseudoheader_t +compile_pseudo_header(transmit_config_t config) +{ + tcp_pseudoheader_t pseudo; + pseudo.src_addr = config.self; + pseudo.dst_addr = config.peer; + pseudo.reserved = 0; + pseudo.protocol = 6; // TCP + pseudo.tcp_length = cpu_to_net_u16(sizeof(tcp_segment_t) + config.payload.len); + return pseudo; +} + static tcp_segment_t -compile_segment(uint32_t wnd, uint8_t flags, - uint16_t sport, uint16_t dport, - uint32_t seq_no, uint32_t ack_no) +compile_segment(transmit_config_t config) { tcp_segment_t header; - //memset(header, 0, sizeof(tcp_segment_t)); int offset = 5; // No options - header.src_port = cpu_to_net_u16(sport); - header.dst_port = cpu_to_net_u16(dport); - header.flags = flags; - header.seq_no = cpu_to_net_u32(seq_no); - header.ack_no = cpu_to_net_u32(ack_no); + header.src_port = cpu_to_net_u16(config.src_port); + header.dst_port = cpu_to_net_u16(config.dst_port); + header.flags = config.flags; + header.seq_no = cpu_to_net_u32(config.seq); + header.ack_no = cpu_to_net_u32(config.ack); header.offset1 = cpu_is_little_endian() ? 0 : offset; header.offset2 = cpu_is_little_endian() ? offset : 0; - header.window = cpu_to_net_u16(wnd); // Why is a 32 bit integer being backed into a 16 bit? + header.window = cpu_to_net_u16(config.window); // Why is a 32 bit integer being backed into a 16 bit? header.checksum = 0; // Will be calculated later header.urgent_pointer = 0; + tcp_pseudoheader_t pseudo = compile_pseudo_header(config); + slice_t list[] = { SLICE(pseudo), SLICE(header), config.payload }; + header.checksum = calc_checksum(list, COUNT(list)); + return header; } -static uint16_t -calculate_header_checksum(tcp_segment_t header, slice_t data, - ip_address_t self_ip, ip_address_t peer_ip) -{ - tcp_pseudoheader_t pseudo; - - pseudo.src_addr = self_ip; - pseudo.dst_addr = peer_ip; - pseudo.reserved = 0; - pseudo.protocol = 6; // TCP - pseudo.tcp_length = cpu_to_net_u16(sizeof(tcp_segment_t) + data.len); - - slice_t list[] = { SLICE(pseudo), SLICE(header), data }; - return calculate_byte_checksum(list, COUNT(list)); -} - -static void -compile_checksum(tcp_segment_t *header, slice_t data, - ip_address_t self_ip, ip_address_t peer_ip) -{ - header->checksum = calculate_header_checksum(*header, data, self_ip, peer_ip); -} - static int -emit_segment_bytes(tcp_state_t *tcp, ip_address_t ip, - tcp_segment_t header, slice_t data) +transmit_bytes(tcp_state_t *tcp, ip_address_t ip, + tcp_segment_t header, slice_t payload) { - slice_t slices[] = { SLICE(header), data }; + slice_t slices[] = { SLICE(header), payload }; return tcp->callbacks.send(tcp->callbacks.data, ip, slices, COUNT(slices)); } -static slice_t -get_pending_output_data(tcp_connection_t *c, size_t max) +static int transmit_basic(tcp_state_t *tcp, transmit_config_t config) { - // [snd_wnd + snd_una - snd_nxt] is the number of bytes that - // are in the output buffer but weren't sent yet - - size_t available; - if (c->snd_nxt <= c->snd_wnd + c->snd_una) - available = c->snd_wnd + c->snd_una - c->snd_nxt; - else - available = 0; - - slice_t s; - s.ptr = c->out_buffer + c->snd_nxt - c->snd_una; - s.len = MIN(max, available); - return s; + tcp_segment_t header = compile_segment(config); + return transmit_bytes(tcp, config.peer, header, config.payload); } -#ifdef TCP_DEBUG -static void -emit_segment_inner(tcp_connection_t *c, uint8_t flags, - size_t max_payload, bool retransmit, - const char *file, int line); -#define emit_segment(c, flags, max_payload, retransmit) \ - emit_segment_inner(c, flags, max_payload, retransmit, __FILE__, __LINE__) -#else -static void -emit_segment(tcp_connection_t *c, uint8_t flags, - size_t max_payload, bool retransmit); -#endif - -static void -timeout_callback_retransmit(void *p) +static void transmit_reply_rst(tcp_state_t *tcp, ip_address_t receiver, + tcp_segment_t received, uint32_t seq, uint32_t ack) { - tcp_connection_t *c = p; - TCP_DEBUG_LOG("Retransmitting"); - emit_segment(c, TCP_FLAG_ACK, SIZE_MAX, true); - c->retr_timer = NULL; + uint8_t flags = TCP_FLAG_RST; + if (ack > 0) flags |= TCP_FLAG_ACK; + + transmit_config_t config = { + .self = tcp->ip, + .peer = receiver, + .src_port = received.dst_port, + .dst_port = received.src_port, + .seq = seq, + .ack = ack, + .flags = flags, + .window = 0, + .payload = SLICE_EMPTY, + }; + transmit_basic(tcp, config); } -static uint64_t -calc_retr_timeout(tcp_connection_t *c) +static void transmit_rst(tcp_connection_t *c, bool ack) { - #define MIN_RETRANSMISSION_TIMEOUT 1000 - #define MAX_RETRANSMISSION_TIMEOUT 60000 - - uint64_t timeout = c->estimated_rtt + 4 * c->estimated_dev; - timeout = MIN(timeout, MAX_RETRANSMISSION_TIMEOUT); - timeout = MAX(timeout, MIN_RETRANSMISSION_TIMEOUT); - return timeout; -} - -static void -start_retr_timer(tcp_connection_t *c) -{ - tcp_state_t *state = c->listener->state; - - uint64_t timeout = calc_retr_timeout(c); - tcp_timer_t *timer = tcp_timer_create(&state->timers, timeout, "retr", - timeout_callback_retransmit, c); - if (timer == NULL) - TCP_DEBUG_LOG("Failed to start retransmission timer"); - else - TCP_DEBUG_LOG("Retransmission timer started (%d ms)", (int) timeout); - c->retr_timer = timer; -} - -static void -stop_retr_timer(tcp_connection_t *c) -{ - if (c->retr_timer) { - TCP_DEBUG_LOG("Retransmission timer disabled"); - tcp_timer_disable(c->retr_timer); - c->retr_timer = NULL; - } -} - -static slice_t -get_output_data(tcp_connection_t *c) -{ - slice_t s; - s.ptr = c->out_buffer; - s.len = c->snd_wnd; - return s; -} - -// Send a tcp segment on the connection -// with a maximum payload of "payload" and -// the specified flags. - -#ifdef TCP_DEBUG -static void -emit_segment_inner(tcp_connection_t *c, uint8_t flags, - size_t max_payload, bool retransmit, - const char *file, int line) -{ - { - char peer_ip[16]; - snprintf(peer_ip, sizeof(peer_ip), - "%d.%d.%d.%d", - c->peer_ip >> 24 & 0xff, - c->peer_ip >> 16 & 0xff, - c->peer_ip >> 8 & 0xff, - c->peer_ip >> 0 & 0xff); - - const char *flags_str; - switch (flags) { - case TCP_FLAG_FIN: flags_str = "FUN"; break; - case TCP_FLAG_SYN: flags_str = "SYN"; break; - case TCP_FLAG_RST: flags_str = "RST"; break; - case TCP_FLAG_PUSH: flags_str = "PUSH"; break; - case TCP_FLAG_ACK: flags_str = "ACK"; break; - case TCP_FLAG_URG: flags_str = "URG"; break; - case TCP_FLAG_SYN - | TCP_FLAG_ACK: flags_str = "SYN|ACK"; break; - case TCP_FLAG_FIN - | TCP_FLAG_ACK: flags_str = "FIN|ACK"; break; - default: flags_str="??"; break; - } - - TCP_DEBUG_LOG("emit_segment(peer=%s:%d, flags=%s, retransmit=%s) in %s:%d", - peer_ip, c->peer_port, flags_str, retransmit ? "true" : "false", - file, line); - } -#else -static void -emit_segment(tcp_connection_t *c, uint8_t flags, - size_t max_payload, bool retransmit) -{ -#endif tcp_listener_t *listener = c->listener; - tcp_state_t *state = listener->state; + tcp_state_t *tcp = listener->state; - slice_t data; - if (retransmit) { - data = get_output_data(c); - if (c->in_buffer_syn) flags |= TCP_FLAG_SYN; - if (c->in_buffer_fin) flags |= TCP_FLAG_FIN; - } else - data = get_pending_output_data(c, max_payload); + uint8_t flags = TCP_FLAG_RST; - uint16_t sport = listener->port; - uint16_t dport = c->peer_port; - uint32_t ack_no = (flags & TCP_FLAG_ACK) ? c->rcv_nxt : 0; - uint32_t seq_no = c->snd_nxt; + if (ack) + flags |= TCP_FLAG_ACK; - ip_address_t self_ip = state->ip; - ip_address_t peer_ip = c->peer_ip; + transmit_config_t config = { + .self = tcp->ip, + .peer = c->peer_ip, + .src_port = listener->port, + .dst_port = c->peer_port, + .seq = c->snd_nxt, + .ack = ack ? c->rcv_nxt : 0, + .flags = flags, + .window = c->rcv_wnd, + .payload = SLICE_EMPTY, + }; + transmit_basic(tcp, config); - tcp_segment_t header = compile_segment(c->rcv_wnd, flags, - sport, dport, - seq_no, ack_no); - compile_checksum(&header, data, - self_ip, peer_ip); + if (flags & TCP_FLAG_ACK) + c->last_acked = c->rcv_nxt; +} + +#ifdef TCP_DEBUG +#define TCP_DEBUG_LOCATION(F, L) , const char *F, int L +#else +#define TCP_DEBUG_LOCATION(F, L) +#endif + +static const char *flags_to_str(uint8_t flags) +{ + switch (flags) { + + case TCP_FLAG_ACK: return "ACK"; + case TCP_FLAG_FIN: return "FIN"; + case TCP_FLAG_SYN: return "SYN"; + case TCP_FLAG_RST: return "RST"; + case TCP_FLAG_URG: return "URG"; + + case TCP_FLAG_FIN | TCP_FLAG_ACK: return "FIN|ACK"; + case TCP_FLAG_SYN | TCP_FLAG_ACK: return "SYN|ACK"; + case TCP_FLAG_RST | TCP_FLAG_ACK: return "RST|ACK"; + + default: return "?"; + } + +} + +static void transmit_(tcp_connection_t *c, uint8_t flags, bool no_payload + TCP_DEBUG_LOCATION(file, line)) +{ + TCP_DEBUG_LOG("transmit(flags=%s, no_payload=%s) at %s:%d", flags_to_str(flags), no_payload ? "true" : "false", file, line); + + tcp_listener_t *listener = c->listener; + tcp_state_t *tcp = listener->state; + + if (flags == TCP_FLAG_ACK && no_payload) { + // The peer is requesting a segment just containing an ACK + // If there are no additional bytes to ACK, ignore the + // transmission request. + if (c->last_acked == c->rcv_nxt) + return; + } + + bool ack = flags & TCP_FLAG_ACK; + + size_t waiting_to_be_sent = c->oused - (c->snd_nxt - c->snd_una); + + // Choose a slice of the transmission queue + slice_t payload; + { + size_t num; + if (no_payload) + num = 0; + else { + size_t mss = 500; + num = MIN(mss, waiting_to_be_sent); + } + payload.ptr = c->odata + c->snd_nxt - c->snd_una; + payload.len = num; + } + + bool sending_everything = (waiting_to_be_sent == payload.len); + if (sending_everything && c->send_fin_when_fully_flushed) { + + // In a previous transmission the caller requested + // a FIN to be sent when the transmission queue was + // emptied. This transmission will send all of the + // pending data. + flags |= TCP_FLAG_FIN; + c->send_fin_when_fully_flushed = false; + c->waiting_ack_for_fin = true; + + } else if (flags & TCP_FLAG_FIN) { + + // The caller requested a FIN to be sent, + // but if there is some data in the transmission + // queue, the FIN should actually be sent + // after everything else is sent. + if (sending_everything) + c->waiting_ack_for_fin = true; + else { + c->send_fin_when_fully_flushed = true; + flags &= ~TCP_FLAG_FIN; // FIN will be sent in one of the next transmissions + } + } + + // Peer don't accept FINs if not accompanied by ACKs + if (flags & TCP_FLAG_FIN) flags |= TCP_FLAG_ACK; + + transmit_config_t config = { + .self = tcp->ip, + .peer = c->peer_ip, + .src_port = listener->port, + .dst_port = c->peer_port, + .seq = c->snd_nxt, + .ack = ack ? c->rcv_nxt : 0, + .flags = flags, + .window = c->rcv_wnd, + .payload = payload, + }; + transmit_basic(tcp, config); + + c->snd_nxt += payload.len; + + if (flags & TCP_FLAG_FIN) { + c->snd_nxt++; + if (c->state == TCP_STATE_CLOSE_WAIT) + // This state change was queued in the + // [tcp_connection_destroy] function. + c->state = TCP_STATE_LAST_ACK; + } + + if (flags & TCP_FLAG_SYN) { + c->snd_nxt++; + c->waiting_ack_for_syn = true; + } + + if (flags & TCP_FLAG_ACK) + c->last_acked = c->rcv_nxt; + + // Restart the retransmission timer if necessary + if (c->snd_una < c->snd_nxt) { + + if (c->retr_timer) + tcp_timer_disable(c->retr_timer); + + size_t ms = 1000; // Should be adaptative + c->retr_timer = tcp_timer_create(&tcp->timers, ms, "retr", retr_timeout_callback, c); + if (c->retr_timer == NULL) + TCP_DEBUG_LOG("Couldn't set retransmission timer"); + } +} + +#ifdef TCP_DEBUG +#define transmit(c, flags, no_payload) transmit_((c), (flags), (no_payload), __FILE__, __LINE__) +#else +#define transmit transmit_ +#endif + +static void retransmit(tcp_connection_t *c) +{ + tcp_listener_t *listener = c->listener; + tcp_state_t *tcp = listener->state; + + size_t retr_queue_bytes = c->snd_nxt - c->snd_una; - int result = emit_segment_bytes(state, peer_ip, header, data); + assert(retr_queue_bytes > 0); // If there were no bytes to ACK, + // there would be no active timer. - if (result < 0) - return; // It wasn't possible to send out bytes. - // We'll try again later! + size_t retr_queue_ghost = 0; + if (c->waiting_ack_for_syn) retr_queue_ghost++; + if (c->waiting_ack_for_fin) retr_queue_ghost++; - if (result < (int) sizeof(tcp_segment_t)) { // What about options?? - // Not even the TCP header was sent. - // I hope this nexer happens! - assert(0); - return; - } - - size_t sent_data = result - sizeof(tcp_segment_t); - if (flags & TCP_FLAG_SYN) sent_data++; - if (flags & TCP_FLAG_FIN) sent_data++; - - if (!retransmit) { - c->snd_nxt += sent_data; - if (flags & TCP_FLAG_SYN) c->in_buffer_syn = true; - if (flags & TCP_FLAG_FIN) c->in_buffer_fin = true; - } - - bool expect_ack = (sent_data > 0); - - if (expect_ack) - // Something that needs to be ACKed by peer was sent. - // Start the retransmission timer. - start_retr_timer(c); - - if (c->calculating_rtt == false && expect_ack) { - c->calculating_rtt = true; - c->rtt_calc_seq = seq_no; - c->rtt_calc_time = c->listener->state->timers.current_time_ms; + assert(retr_queue_bytes >= retr_queue_ghost); + + + size_t retr_queue_actual = retr_queue_bytes - retr_queue_ghost; + assert(retr_queue_actual <= c->oused); + + size_t mss = 500; // TODO: Make this configurable + size_t num = MIN(mss, retr_queue_actual); + + slice_t payload = {.ptr=c->odata, .len=num}; + + uint8_t flags = TCP_FLAG_ACK; + + // If we're sending the last portion of + // the retransmission queue and a FIN was + // sent but not ACKed, send it again. + if (num == retr_queue_actual && c->waiting_ack_for_fin) + flags |= TCP_FLAG_FIN; + + transmit_config_t config = { + .self = tcp->ip, + .peer = c->peer_ip, + .src_port = listener->port, + .dst_port = c->peer_port, + .seq = c->snd_una, + .ack = c->rcv_nxt, + .flags = flags, + .window = c->rcv_wnd, + .payload = payload, + }; + transmit_basic(tcp, config); +} + +static void forget_acked(tcp_connection_t *c, uint32_t ack) +{ + if (ack <= c->snd_una) + return; // Duplicate ACK + + size_t acked_num = ack - c->snd_una; + + size_t ghost = 0; // Number of acked ghost (to be calculated) + bool acked_fin = c->waiting_ack_for_fin && ack == c->snd_nxt; + bool acked_syn = c->waiting_ack_for_syn && acked_num > 0; + if (acked_fin) { ghost++; c->waiting_ack_for_fin = false; } + if (acked_syn) { ghost++; c->waiting_ack_for_syn = false; } + assert(acked_num >= ghost); + + size_t acked_num_no_ghost = acked_num - ghost; + assert(acked_num_no_ghost <= c->oused); + + memmove(c->odata, + c->odata + acked_num_no_ghost, + c->oused - acked_num_no_ghost); + + c->snd_una = ack; + c->oused -= acked_num_no_ghost; +/* + if (acked_syn && acked_fin) { + TCP_DEBUG_LOG("Peer acked %ld bytes (including SYN and FIN)", acked_num); + } else if (acked_syn) { + TCP_DEBUG_LOG("Peer acked %ld bytes (including SYN)", acked_num); + } else if (acked_fin) { + TCP_DEBUG_LOG("Peer acked %ld bytes (including FIN)", acked_num); + } else { + TCP_DEBUG_LOG("Peer acked %ld bytes", acked_num); } +*/ } static slice_t get_input_data(tcp_connection_t *c) { slice_t s; - s.ptr = c->in_buffer; - s.len = TCP_INPUT_BUFFER_SIZE - c->rcv_wnd; + s.ptr = c->idata; + s.len = TCP_IBUFFER_SIZE - c->rcv_wnd; return s; } static size_t -move_data_from_input_buffer(tcp_connection_t *c, slice_t dst) +move_from_idata(tcp_connection_t *c, slice_t dst) { slice_t buf = get_input_data(c); size_t unread = (c->rcv_nxt - c->rcv_unread); @@ -451,7 +538,7 @@ move_data_from_input_buffer(tcp_connection_t *c, slice_t dst) if (moving > 0) { memcpy(dst.ptr, buf.ptr, moving); - memmove(buf.ptr, buf.ptr + moving, buf.len - moving); + memmove(buf.ptr, (char*) buf.ptr + moving, buf.len - moving); c->rcv_wnd += moving; c->rcv_unread += moving; } @@ -466,7 +553,7 @@ move_data_into_input_buffer(tcp_connection_t *c, slice_t src) size_t moving = MIN(src.len, c->rcv_wnd); if (moving > 0) { - memcpy(buf.ptr + buf.len, src.ptr, moving); + memcpy((char*) buf.ptr + buf.len, src.ptr, moving); c->rcv_wnd -= moving; c->rcv_nxt += moving; } @@ -475,145 +562,50 @@ move_data_into_input_buffer(tcp_connection_t *c, slice_t src) } static tcp_connection_t* -find_connection(tcp_listener_t *listener, ip_address_t peer_ip, uint16_t peer_port) +look_for_connection_in_list(tcp_connection_t *list, ip_address_t ip, uint16_t port) { - tcp_connection_t *connection; - - // Check in the accepted list - connection = listener->accepted_list; - while (connection) { - if (connection->peer_port == peer_port && - connection->peer_ip == peer_ip) + tcp_connection_t *c = list; + while (c) { + if (c->peer_ip == ip && c->peer_port == port) break; - connection = connection->next; + c = c->next; } - if (connection) { - // accepted=true - } else { - // accepted=false - connection = listener->non_accepted_queue_head; - while (connection) { - if (connection->peer_port == peer_port && - connection->peer_ip == peer_ip) - break; - connection = connection->next; - } - } - if (connection) { - // established=true - } else { - // established=false - connection = listener->non_established_list; - while (connection) { - if (connection->peer_port == peer_port && - connection->peer_ip == peer_ip) - break; - connection = connection->next; - } - } - return connection; + return c; +} + +static tcp_connection_t* +find_connection(tcp_listener_t *listener, ip_address_t ip, uint16_t port) +{ + tcp_connection_t *c = look_for_connection_in_list(listener->accepted, ip, port); + if (c == NULL) c = look_for_connection_in_list(listener->qhead, ip, port); + if (c == NULL) c = look_for_connection_in_list(listener->noestab, ip, port); + return c; } static void -move_from_non_established_list_to_non_accepted_queue(tcp_connection_t *connection) +move_from_non_established_list_to_non_accepted_queue(tcp_connection_t *c) { - tcp_listener_t *listener = connection->listener; + tcp_listener_t *listener = c->listener; // Unlink the structure from the non established list { - if (connection->prev) - connection->prev->next = connection->next; + if (c->prev) + c->prev->next = c->next; else - listener->non_established_list = connection->next; + listener->noestab = c->next; - if (connection->next) - connection->next->prev = connection->prev; + if (c->next) + c->next->prev = c->prev; } // Push it into the non accepted queue - connection->prev = NULL; - connection->next = listener->non_accepted_queue_head; - if (listener->non_accepted_queue_head) - listener->non_accepted_queue_head->prev = connection; + c->prev = NULL; + c->next = listener->qhead; + if (listener->qhead) + listener->qhead->prev = c; else - listener->non_accepted_queue_tail = connection; - listener->non_accepted_queue_head = connection; -} - -static void -update_rtt_estimation(tcp_connection_t *c, uint64_t sample_rtt) -{ - uint64_t estimated_rtt = c->estimated_rtt; - uint64_t estimated_dev = c->estimated_dev; - - uint64_t sample_dev = ABS((int) sample_rtt - (int) estimated_rtt); - - float a = 0.9; - float b = 0.125; - - estimated_rtt = a * sample_rtt + (1 - a) * estimated_rtt; - estimated_dev = b * sample_dev + (1 - b) * estimated_dev; - - c->estimated_rtt = estimated_rtt; - c->estimated_dev = estimated_dev; -} - -static bool ack_until(tcp_connection_t *c, uint32_t ack_no) -{ - // Set sent but unacknowledged bytes with sequence - // numbers up to [ack_no] as acknowledged. This [ack_no] - // comes from the network so it must be verified. - // - // If at least one byte was marked as acknowledged, then - // true is returned, else false. - - if (ack_no <= c->snd_una) { - TCP_DEBUG_LOG("Received segment acknowledged again %d", ack_no); - return false; - } - - if (ack_no > c->snd_nxt) { - // Peer ACKed unsent data. The right course of action - // is probably to drop the c. - TCP_DEBUG_LOG("Received segment acknowledged unsent data " - "with sequence number %d, but %d still wasn't sent", - ack_no, c->snd_nxt); - return false; // For now we'll just ignore the segment. - } - - size_t newly_acked_bytes = ack_no - c->snd_una; - - if (ack_no > 1) { - - // Only remove data from the output buffer when - // the acked data wasn't a ghost byte - - memmove(c->out_buffer, - c->out_buffer + newly_acked_bytes, - c->snd_wnd - newly_acked_bytes); - - c->snd_wnd -= newly_acked_bytes; - } - c->snd_una = ack_no; - c->in_buffer_syn = false; - - if (ack_no >= c->snd_nxt) { // snd_nxt-1? - // Peer acked everything there was to ack - stop_retr_timer(c); - c->in_buffer_fin = false; - } - - if (c->calculating_rtt) { - if (ack_no >= c->rtt_calc_seq) { - uint64_t time_beg = c->rtt_calc_time; - uint64_t time_end = c->listener->state->timers.current_time_ms; - uint64_t sample_rtt = (time_end - time_beg); - update_rtt_estimation(c, sample_rtt); - c->calculating_rtt = false; - } - } - - return true; + listener->qtail = c; + listener->qhead = c; } static void @@ -621,6 +613,8 @@ really_close_listener(tcp_listener_t *listener) { tcp_state_t *state = listener->state; + assert(listener->count == 0); + // Pop listener from used list { // Update the reference to the listener of @@ -641,16 +635,11 @@ really_close_listener(tcp_listener_t *listener) 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_DEBUG_LOG("Connection closed"); + tcp_listener_t *listener = connection->listener; tcp_state_t *state = listener->state; @@ -658,57 +647,35 @@ really_close_connection(tcp_connection_t *connection) tcp_connection_t *next = connection->next; if (connection->prev) 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; + else if (listener->accepted == connection) + listener->accepted = next; + else if (listener->noestab == connection) + listener->noestab = next; + else if (listener->qhead == connection) + listener->qhead = 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; + else if (listener->qtail == connection) + listener->qtail = prev; // Push it into the free connection list connection->prev = NULL; connection->next = state->free_connection_list; state->free_connection_list = connection; + // TODO: Disable all timers + + assert(listener->count > 0); + listener->count--; + // 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)) + if (listener->closed == true && listener->count == 0) really_close_listener(listener); } -static void timeout_callback_time_wait(void *data) -{ - tcp_connection_t *connection = data; - assert(connection->state == TCP_STATE_TIME_WAIT); - - TCP_DEBUG_LOG("TIME-WAIT -> CLOSED"); - - // We can finally free up this connection structure! - really_close_connection(connection); -} - -static void -change_state_to_time_wait(tcp_connection_t *c) -{ - tcp_state_t *state = c->listener->state; - c->state = TCP_STATE_TIME_WAIT; - - stop_retr_timer(c); - - // 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, "wait", - timeout_callback_time_wait, c)) { - assert(0); - } -} - static slice_t segment_payload(tcp_segment_t *segment, size_t len) { @@ -737,282 +704,741 @@ segment_payload(tcp_segment_t *segment, size_t len) return payload; } -static bool -set(tcp_segment_t *segment, int flags) +static bool set(tcp_segment_t *segment, uint8_t flags) { - return segment->flags & flags != 0; + return (segment->flags & flags) != 0; } -static bool -issyn(tcp_segment_t *segment) +static void unset(tcp_segment_t *segment, uint8_t flags) { - return set(segment, TCP_FLAG_SYN); + segment->flags &= ~flags; } -static bool -isack(tcp_segment_t *segment) +static void state_closed(tcp_state_t *tcp, ip_address_t sender, + tcp_segment_t *seg, size_t len) { - return set(segment, TCP_FLAG_ACK); + slice_t payload = segment_payload(seg, len); + + // From RFC 9239, Section 3.10.7.1. + // + // If the state is CLOSED (i.e., TCB does not exist), then + // + // all data in the incoming segment is discarded. An incoming + // segment containing a RST is discarded. An incoming segment + // not containing a RST causes a RST to be sent in response. + // The acknowledgment and sequence field values are selected + // to make the reset sequence acceptable to the TCP endpoint + // that sent the offending segment. + // + // If the ACK bit is off, sequence number zero is used, + // + // + // + // If the ACK bit is on, + // + // + // + // Return. + + if (!set(seg, TCP_FLAG_RST)) + // Didn't receive a reset so we need to send one. + transmit_reply_rst(tcp, sender, *seg, + set(seg, TCP_FLAG_ACK) ? seg->ack_no : 0, + set(seg, TCP_FLAG_ACK) ? 0 : seg->seq_no + payload.len); } -static bool -isfin(tcp_segment_t *segment) +static tcp_connection_t* +state_listen(tcp_listener_t *listener, ip_address_t sender, + tcp_segment_t *seg) { - return set(segment, TCP_FLAG_FIN); -} + tcp_state_t *tcp = listener->state; + uint16_t sport = net_to_cpu_u16(seg->src_port); -void tcp_process_segment(tcp_state_t *state, ip_address_t sender, - tcp_segment_t *segment, size_t len) -{ -// TCP_DEBUG_LOG("Received TCP segment"); + // From RFC 9239, Section 3.10.7.2. + // + // If the state is LISTEN, then + // + // First, check for a RST: + // + // An incoming RST segment could not be valid since it + // could not have been sent in response to anything sent + // by this incarnation of the connection. An incoming RST + // should be ignored. Return. + // + // Second, check for an ACK: + // + // Any acknowledgment is bad if it arrives on a connection + // still in the LISTEN state. An acceptable reset segment + // should be formed for any arriving ACK-bearing segment. + // The RST should be formatted as follows: + // + // + // + // Return. + // + // Third, check for a SYN: + // + // If the SYN bit is set, check the security. If the + // security/compartment on the incoming segment does not + // exactly match the security/compartment in the TCB, then + // send a reset and return. + // + // + // + // Set RCV.NXT to SEG.SEQ+1, IRS is set to SEG.SEQ, and + // any other control or text should be queued for processing + // later. ISS should be selected and a SYN segment sent + // of the form: + // + // + // + // SND.NXT is set to ISS+1 and SND.UNA to ISS. The connection + // state should be changed to SYN-RECEIVED. Note that any + // other incoming control or data (combined with SYN) will + // be processed in the SYN-RECEIVED state, but processing of + // SYN and ACK should not be repeated. If the listen was not + // fully specified (i.e., the remote socket was not fully + // specified), then the unspecified fields should be filled + // in now. + // + // Fourth, other data or control: + // + // This should not be reached. Drop the segment and return. + // Any other control or data-bearing segment (not containing SYN) + // must have an ACK and thus would have been discarded by the ACK + // processing in the second step, unless it was first discarded by + // RST checking in the first step. - { - tcp_timer_t *timer = state->timers.used_list; - TCP_DEBUG_LOG("Timers: [ "); - while (timer) { - TCP_DEBUG_LOG(" %s - %2.2fs", timer->name, (float) (timer->deadline - state->timers.current_time_ms) / 1000); - timer = timer->next; - } - TCP_DEBUG_LOG("]"); + if (set(seg, TCP_FLAG_RST)) + return NULL; + + if (set(seg, TCP_FLAG_ACK)) { + transmit_reply_rst(tcp, sender, *seg, 0, seg->ack_no); + return NULL; } - slice_t payload = segment_payload(segment, len); - - uint16_t dport = net_to_cpu_u16(segment->dst_port); - uint16_t sport = net_to_cpu_u16(segment->src_port); + if (set(seg, TCP_FLAG_SYN)) { + // TODO: Check for "security/compartment" and "seg precedence value" - tcp_listener_t *listener = find_listener(state, dport); - if (listener == NULL) - return; // No connection is listening on this port. - // Silently drop the segment - - tcp_connection_t *connection = find_connection(listener, sender, sport); - if (!connection) { - - if (listener->closed) - // Listener was closed by the user, so already - // open connections are ok but new ones are not - // allowed. - return; - - // Something sent to an open listener. - - // We expect it to be a request to connect, - // which means that the segment should have - // the SYN flag high (and only that one). - // If that's true, a connection object must - // be instanciated and a SYN|ACK message sent. - // - // Alongside the SYN, some payload may be - // associated with the message. Though we must - // make sure that this data isn't delivered to - // the parent application until the connection - // is fully established. - // - // From RFC 793, section 3.4: - // - // > Several examples of connection initiation follow. Although these - // > examples do not show connection synchronization using data-carrying - // > segments, this is perfectly legitimate, so long as the receiving TCP - // > doesn't deliver the data to the user until it is clear the data is - // > valid (i.e., the data must be buffered at the receiver until the - // > connection reaches the ESTABLISHED state). The three-way handshake - // > reduces the possibility of false connections. - // - // (https://www.ietf.org/rfc/rfc793.txt) - - if (!issyn(segment)) { - // Received message isn't SYN. Ignore the segment. - TCP_DEBUG_LOG("Connection request is missing the SYN flag"); - return; - } - - if (set(segment, ~TCP_FLAG_SYN)) - TCP_DEBUG_LOG("Connection request segment has flags other than SYN set"); - - if (payload.len > 0) { - TCP_DEBUG_LOG("Connection request segment has some payload. " - "This is a valid behaviour but we don't handle " - "that case yet. Droppong the connection"); - return; - } - - uint32_t seq_no = choose_sequence_no(); - uint32_t ack_no = net_to_cpu_u32(segment->seq_no)+1; + uint32_t seq = choose_sequence_no(); + uint32_t ack = net_to_cpu_u32(seg->seq_no)+1; - connection = connection_create(listener, seq_no, ack_no, sender, sport); - if (connection == NULL) { + tcp_connection_t *c = connection_create(listener, sender, sport, seq, ack); + if (c == NULL) { TCP_DEBUG_LOG("Connection limit reached"); // Should we let the peer know what happened? - return; + return NULL; } - connection->state = TCP_STATE_SYN_RCVD; - emit_segment(connection, TCP_FLAG_SYN | TCP_FLAG_ACK, 0, false); - // Instead of incrementing the snd_una for the SYN we just send, - // we'll ignore it and also ignore it when the respective ACK si - // received. + transmit(c, TCP_FLAG_SYN | TCP_FLAG_ACK, true); + c->state = TCP_STATE_SYN_RCVD; + + // Complete the processing in the SYN-RCVD state, but + // don't repeat the SYN and ACK should not be repeated + unset(seg, TCP_FLAG_SYN | TCP_FLAG_ACK); + seg->seq_no = cpu_to_net_u32(ack); + return c; // Continue with the SYN-RCVD state + } - } else { + // Else, drop the segment. + return NULL; +} - // Something sent to an already instanciated - // connection. Since there is an instance, it - // means that at least the first SYN was received - // and a SYN|ACK message was sent, so the - // first state of a connection is SYN_RCVD - switch (connection->state) { - - case TCP_STATE_CLOSED: - // This state is only used for uninitialized - // connection structures. If the code behaves - // well, this code should be unreachable. - assert(0); +static bool seq_in_window(tcp_connection_t *c, uint32_t seq) +{ + return c->rcv_nxt <= seq && seq < c->rcv_nxt + c->rcv_wnd; +} + +static bool valid_seq(tcp_connection_t *c, uint32_t seq, size_t payload_len) +{ + + // From RFC 9293, Section 3.10.7.4. + // + // There are four cases for the acceptability test for an incoming + // segment: + // + // Segment Receive Test + // Length Window + // ------- ------- ------------------------------------------- + // + // 0 0 SEG.SEQ = RCV.NXT + // + // 0 >0 RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND + // + // >0 0 not acceptable + // + // >0 >0 RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND + // or RCV.NXT =< SEG.SEQ+SEG.LEN-1 < RCV.NXT+RCV.WND + + uint32_t head = seq; + uint32_t tail = seq + (payload_len > 0 ? payload_len-1 : 0); + + if (!seq_in_window(c, head) || !seq_in_window(c, tail)) + return false; + + if (c->rcv_wnd == 0) { + + if (payload_len > 0) + // Segment can't be accepted because it has + // payload and the input buffer is full. + return false; + + if (seq == c->rcv_nxt) + // Sequence number is in range of the receive window + // (we did the check before) but when the receive window + // is 0, it needs to be exactly the expected one. + return false; + } + + return true; +} + +static void transition_to_time_wait(tcp_connection_t *c) +{ + tcp_listener_t *l = c->listener; + tcp_state_t *tcp = l->state; + + c->state = TCP_STATE_TIME_WAIT; + + if (c->retr_timer) { + tcp_timer_disable(c->retr_timer); + c->retr_timer = NULL; + } + + size_t ms = 60; + c->wait_timer = tcp_timer_create(&tcp->timers, ms, "wait", wait_timeout_callback, c); + if (c->wait_timer == NULL) { + // Couldn't set the TIME-WAIT timer + TCP_DEBUG_LOG("Couldn't set the TIME-WAIT timer"); + wait_timeout_callback(c); + } +} + +#define DISCARD { return; } + +void tcp_process_segment(tcp_state_t *tcp, ip_address_t sender, + tcp_segment_t *segment, size_t len) +{ + uint16_t dport = net_to_cpu_u16(segment->dst_port); + uint16_t sport = net_to_cpu_u16(segment->src_port); + slice_t payload = segment_payload(segment, len); + + tcp_connstate_t connstate; + + tcp_listener_t *listener = find_listener(tcp, dport); + tcp_connection_t *c = listener ? find_connection(listener, sender, sport) : NULL; + + // If the listener does not exist, then the socket referenced by + // the segment is in the CLOSED state. If the listener exists but + // it's marked as closed (as in "listener->closed = true"), then + // already established connections are ok but new ones are not so + // the listener should be regarded as in the CLOSED state. + + if (listener == NULL) + connstate = TCP_STATE_CLOSED; // Not listening on destination port + else { + if (c == NULL) { + if (listener->closed) + connstate = TCP_STATE_CLOSED; // Listener object exists but it's not listening + else + connstate = TCP_STATE_LISTEN; + } else + connstate = c->state; + } + + uint32_t seq; + uint32_t ack; + switch (connstate) { + + case TCP_STATE_CLOSED: + state_closed(tcp, sender, segment, len); + return; + + case TCP_STATE_SYN_SENT: + // This is the state where we sent SYN to initiate + // the connection with a peer acting as server. + // At the moment "microtcp_connect" isn't implemented + // so this state can never be reached. + assert(0); // UNREACHABLE + DISCARD; + + case TCP_STATE_LISTEN: + c = state_listen(listener, sender, segment); + if (c == NULL || c->state != TCP_STATE_SYN_RCVD) break; - case TCP_STATE_SYN_SENT: - // This is the state where we sent SYN to initiate - // the connection with a peer acting as server. - // At the moment "microtcp_connect" isn't implemented - // so this state can never be reached. - assert(0); // UNREACHABLE - break; - - case TCP_STATE_SYN_RCVD: - // At this point a SYN was received and a SYN|ACK sent. - // We expect an ACK to establish the connection. + TCP_DEBUG_LOG("Connection established"); + /* fallthrough */ - if (isack(segment)) { + case TCP_STATE_SYN_RCVD: /* fallthrough */ + case TCP_STATE_ESTABLISHED: /* fallthrough */ + case TCP_STATE_FIN_WAIT_1: /* fallthrough */ + case TCP_STATE_FIN_WAIT_2: /* fallthrough */ + case TCP_STATE_CLOSE_WAIT: /* fallthrough */ + case TCP_STATE_CLOSING: /* fallthrough */ + case TCP_STATE_LAST_ACK: /* fallthrough */ + case TCP_STATE_TIME_WAIT: - if (payload.len > 0) - TCP_DEBUG_LOG("Incoming segment has a payload alongside the ACK for " - "the SYN we sent. This is valid TCP but we don't support " - "this case yet. We'll just ignore the data. Hopefully " - "the peer will retransmit it"); - - ack_until(connection, net_to_cpu_u32(segment->ack_no)); - move_from_non_established_list_to_non_accepted_queue(connection); - signal_ready_to_accept(listener); - - /* The connection is now established */ - connection->state = TCP_STATE_ESTAB; - } - break; + seq = net_to_cpu_u32(segment->seq_no); - case TCP_STATE_ESTAB: - - if (isack(segment)) { - uint32_t ack_no = net_to_cpu_u32(segment->ack_no); - if (ack_until(connection, ack_no)) - // At least one byte was ACKed, so now there's space - // available in the output buffer - signal_ready_to_send(connection); - } - - size_t moved = move_data_into_input_buffer(connection, payload); - if (moved > 0) { - // Data is ready to be received by the parent application - signal_ready_to_recv(connection); - emit_segment(connection, TCP_FLAG_ACK, SIZE_MAX, false); - } - - if (isfin(segment)) { - // An unsolicited FIN was received. We ACK the FIN and - // set the connection state as waiting to be closed from - // this end. - TCP_DEBUG_LOG("ESTAB -> CLOSE_WAIT"); - connection->rcv_nxt++; // FIN ghost byte - emit_segment(connection, TCP_FLAG_ACK, 0, false); - connection->state = TCP_STATE_CLOSE_WAIT; - } - break; - - case TCP_STATE_FIN_WAIT_1: - // The FIN segment was sent after the user called "close". - // In this state we're expecting the ACK flag for the fin. - // The same message could also contain the peer's FIN. - - if (isack(segment) && isfin(segment)) { - - TCP_DEBUG_LOG("FIN-WAIT-1 -> TIME-WAIT"); - - connection->rcv_nxt++; // FIN ghost byte - emit_segment(connection, TCP_FLAG_ACK, 0, false); - change_state_to_time_wait(connection); - break; - } - - if (isack(segment)) { - TCP_DEBUG_LOG("FIN-WAIT-1 -> FIN-WAIT-2"); - connection->state = TCP_STATE_FIN_WAIT_2; - break; - } - - if (isfin(segment)) { - TCP_DEBUG_LOG("FIN-WAIT-1 -> CLOSING"); - connection->rcv_nxt++; // FIN ghost byte - emit_segment(connection, TCP_FLAG_ACK, 0, false); - connection->state = TCP_STATE_CLOSING; - break; - } - - // Expected FIN and/or ACK but didn't receive them - break; - - case TCP_STATE_FIN_WAIT_2: - // Socket was closed from the user so a FIN was sent. - // The FIN was ACKed and now we're waiting for their - // FIN to ACK. - - if (isfin(segment)) { - connection->rcv_nxt++; // FIN ghost byte - emit_segment(connection, TCP_FLAG_ACK, 0, false); - change_state_to_time_wait(connection); - } - - break; - - case TCP_STATE_CLOSE_WAIT: - // Nothing to be done here! + if (!valid_seq(c, seq, payload.len)) { + // From RFC 9293, Section 3.10.7.4. // - // A FIN was received and ACKed. At this point we're - // waiting for the user to close the socket, so we - // can ignore any incoming segment. + // If an incoming segment is not acceptable, an acknowledgment + // should be sent in reply (unless the RST bit is set, if so + // drop the segment and return): + // + // + // + // After sending the acknowledgment, drop the unacceptable + // segment and return. + if (!set(segment, TCP_FLAG_RST)) + transmit(c, TCP_FLAG_ACK, true); + DISCARD; + } + + // TODO: Trim any segments without the exact sequence number + { + if (c->rcv_nxt < seq) { + // Segment refers to the future. Send an ACK with the expected + // sequence number to let the peer know where we're at. + transmit(c, TCP_FLAG_ACK, true); + DISCARD; + } + + if (c->rcv_nxt > seq) { + + size_t delta = c->rcv_nxt - seq; + + memmove(payload.ptr, payload.ptr + delta, delta); + + payload.len -= delta; + seq += delta; + + } + + assert(seq == c->rcv_nxt); + } + + if (set(segment, TCP_FLAG_RST)) { + switch (c->state) { + + case TCP_STATE_SYN_RCVD: + // From RFC 9293, Section 3.10.7.4. + // + // If this connection was initiated with a passive OPEN + // (i.e., came from the LISTEN state), then return this + // connection to LISTEN state and return. The user need + // not be informed. If this connection was initiated with + // an active OPEN (i.e., came from SYN-SENT state), then + // the connection was refused; signal the user "connection + // refused". In either case, the retransmission queue + // should be flushed. And in the active OPEN case, enter + // the CLOSED state and delete the TCB, and return. + + // Since we don't implement "connect" yet, we can + // assume the connection came from the LISTEN state. + // + // Since the connection was never ESTABLISHED, there + // is no need to inform the user. + really_close_connection(c); + DISCARD; + + case TCP_STATE_ESTABLISHED: /* fallthrough */ + case TCP_STATE_FIN_WAIT_1: /* fallthrough */ + case TCP_STATE_FIN_WAIT_2: /* fallthrough */ + case TCP_STATE_CLOSE_WAIT: + // From RFC 9293, Section 3.10.7.4 + // If the RST bit is set, then any outstanding RECEIVEs and SEND + // should receive "reset" responses. All segment queues should be + // flushed. Users should also receive an unsolicited general + // "connection reset" signal. Enter the CLOSED state, delete the + // TCB, and return. + if (c->cb_event) c->cb_event(c->cb_data, TCP_CONNEVENT_RESET); + really_close_connection(c); + DISCARD; + + case TCP_STATE_CLOSING: /* fallthrough */ + case TCP_STATE_LAST_ACK: /* fallthrough */ + case TCP_STATE_TIME_WAIT: + // From RFC 9293, Section 3.10.7.4. + // If the RST bit is set, then enter the CLOSED state, delete + // the TCB, and return. + really_close_connection(c); + DISCARD; + + case TCP_STATE_CLOSED: + case TCP_STATE_LISTEN: + case TCP_STATE_SYN_SENT: + /* UNREACHABLE */ + assert(0); + DISCARD; + } + } + + // TODO: Check security + + if (set(segment, TCP_FLAG_SYN)) { + bool passive_open = true; + if (connstate == TCP_STATE_SYN_RCVD && passive_open) { + // From RFC 9293, Section 3.10.7.4. + // + // If the connection was initiated with a passive OPEN, then + // return this connection to the LISTEN state and return. + // Otherwise, handle per the directions for synchronized states + // below. + really_close_connection(c); + DISCARD; + + } else { + // From RFC 9293, Section 3.10.7.4. + // + // If the SYN bit is set in these synchronized states, it may + // be either a legitimate new connection attempt (e.g., in the + // case of TIME-WAIT), an error where the connection should be + // reset, or the result of an attack attempt, as described in + // RFC 5961 [9]. For the TIME-WAIT state, new connections can + // be accepted if the Timestamp Option is used and meets expectations + // (per [40]). For all other cases, RFC 5961 provides a mitigation + // with applicability to some situations, though there are also + // alternatives that offer cryptographic protection (see Section 7). + // RFC 5961 recommends that in these synchronized states, if + // the SYN bit is set, irrespective of the sequence number, TCP + // endpoints MUST send a "challenge ACK" to the remote peer: + // + // + // + // After sending the acknowledgment, TCP implementations MUST + // drop the unacceptable segment and stop processing further. + // Note that RFC 5961 and Errata ID 4772 [99] contain additional + // ACK throttling notes for an implementation. + // + // For implementations that do not follow RFC 5961, the original + // behavior described in RFC 793 follows in this paragraph. If + // the SYN is in the window it is an error: send a reset, any + // outstanding RECEIVEs and SEND should receive "reset" responses, + // all segment queues should be flushed, the user should also + // receive an unsolicited general "connection reset" signal, + // enter the CLOSED state, delete the TCB, and return. + // + // If the SYN is not in the window, + // this step would not be reached and an ACK would have been sent + // in the first step (sequence number check). + // + // This is a minimal implementation of TCP, so we only + // implement the RFC 793 part. + transmit_rst(c, false); + if (c->cb_event) c->cb_event(c->cb_data, TCP_CONNEVENT_RESET); + really_close_connection(c); + DISCARD; + } + } + + // From RFC 9293, Section 3.10.7.4. + // + // if the ACK bit is off, + // + // drop the segment and return + // + if (!set(segment, TCP_FLAG_ACK)) + DISCARD; // Drop the segment + + ack = net_to_cpu_u32(segment->ack_no); + switch (c->state) { + + case TCP_STATE_CLOSED: + case TCP_STATE_LISTEN: + case TCP_STATE_SYN_SENT: + /* UNREACHABLE */ + assert(0); + DISCARD; + + case TCP_STATE_SYN_RCVD: + // From RFC 9293, Section 3.10.7.4. + // + // If SND.UNA < SEG.ACK =< SND.NXT, then enter ESTABLISHED state + // and continue processing with the variables below set to: + // + // SND.WND <- SEG.WND + // SND.WL1 <- SEG.SEQ + // SND.WL2 <- SEG.ACK + // + // If the segment acknowledgment is not acceptable, form a reset + // segment + // + // + // + // and send it. + + if (c->snd_una >= ack || ack > c->snd_nxt) { + // Segment isn't acceptable + transmit_reply_rst(tcp, sender, *segment, ack, 0); + DISCARD; + } + + c->state = TCP_STATE_ESTABLISHED; + c->snd_wnd = net_to_cpu_u16(segment->window); + c->snd_wl1 = seq; + c->snd_wl2 = ack; + + // Move connection from the non-established to + // the queue + move_from_non_established_list_to_non_accepted_queue(c); + listener->cb_event(listener->cb_data, TCP_LISTENEVENT_ACCEPT); + + /* fallthrough */ + + case TCP_STATE_ESTABLISHED: + case TCP_STATE_FIN_WAIT_1: + case TCP_STATE_FIN_WAIT_2: + case TCP_STATE_CLOSE_WAIT: + case TCP_STATE_CLOSING: + // From RFC 9293, Section 3.10.7.4. + // + // If SND.UNA < SEG.ACK =< SND.NXT, then set SND.UNA <- SEG.ACK. + // Any segments on the retransmission queue that are thereby entirely + // acknowledged are removed. Users should receive positive acknowledgments + // for buffers that have been SENT and fully acknowledged (i.e., + // SEND buffer should be returned with "ok" response). If the ACK + // is a duplicate (SEG.ACK =< SND.UNA), it can be ignored. If the + // ACK acks something not yet sent (SEG.ACK > SND.NXT), then send + // an ACK, drop the segment, and return. + // + // If SND.UNA =< SEG.ACK =< SND.NXT, the send window should be + // updated. If (SND.WL1 < SEG.SEQ or (SND.WL1 = SEG.SEQ and + // SND.WL2 =< SEG.ACK)), set SND.WND <- SEG.WND, set SND.WL1 <- SEG.SEQ, + // and set SND.WL2 <- SEG.ACK. + // + // Note that SND.WND is an offset from SND.UNA, that SND.WL1 records + // the sequence number of the last segment used to update SND.WND, + // and that SND.WL2 records the acknowledgment number of the last + // segment used to update SND.WND. The check here prevents using + // old segments to update the window. + + if (ack > c->snd_nxt) { + // Peer acked something not sent yet + transmit(c, TCP_FLAG_ACK, true); + DISCARD; + } + + if (ack > c->snd_una) { + + /* ACK isn't a duplicate */ + + forget_acked(c, ack); + + // If everything was ACKed, disable the retransmission timer + if (ack == c->snd_nxt) + if (c->retr_timer) { + tcp_timer_disable(c->retr_timer); + c->retr_timer = NULL; + } + + // Update send window + if (c->snd_wl1 < seq || (c->snd_wl1 == seq && c->snd_wl2 <= ack)) { + + c->snd_wnd = net_to_cpu_u16(segment->window); + c->snd_wl1 = seq; + c->snd_wl2 = ack; + } + } + + switch (c->state) { + case TCP_STATE_FIN_WAIT_1: + // From RFC 9293, Section 3.10.7.4. + // + // In addition to the processing for the ESTABLISHED state, if the + // FIN segment is now acknowledged, then enter FIN-WAIT-2 and + // continue processing in that state. + + if (ack < c->snd_nxt) + break; // Not everything was ACKed by the peer, so the FIN wasn't ACKed + + // The FIN was ACKed so it's possible to transition to the + // FIN-WAIT-2 state. + c->state = TCP_STATE_FIN_WAIT_2; + + /* fallthrough */ + + case TCP_STATE_FIN_WAIT_2: + // From RFC 9293, Section 3.10.7.4. + // In addition to the processing for the ESTABLISHED state, if the + // retransmission queue is empty, the user's CLOSE can be acknowledged + // ("ok") but do not delete the TCB. + // + // Nothing to be done! The fact we sent a FIN implies that + // the user dropped the reference to the tcp socket, so + // there's nothing to acknowledge. + break; + + case TCP_STATE_CLOSING: + + // From RFC 9293, Section 3.10.7.4. + // + // In addition to the processing for the ESTABLISHED state, if the + // ACK acknowledges our FIN, then enter the TIME-WAIT state; + // otherwise, ignore the segment. + + if (ack == c->snd_nxt) { + // Everything was ACKed, so if a FIN was sent that was ACKed too. + transition_to_time_wait(c); + } + DISCARD; // Ignore the segment + + default: + break; + } break; case TCP_STATE_LAST_ACK: - // FIN was received, ACKed and a FIN was sent. - // Now we expect the final ACK for our FIN. + // From RFC 9293, Section 3.10.7.4. + // The only thing that can arrive in this state is an acknowledgment + // of our FIN. If our FIN is now acknowledged, delete the TCB, enter + // the CLOSED state, and return. - if (isack(segment)) { - uint32_t ack_no = net_to_cpu_u32(segment->ack_no); - if (ack_until(connection, ack_no)) { - // FIXME! - // At least one byte was ACKed. There is not assurance - // that it was the FIN, but it's good enough for now! - connection->state = TCP_STATE_CLOSED; - really_close_connection(connection); + if (ack == c->snd_nxt) + // Everything was ACKed by peer, so the FIN also. + really_close_connection(c); + DISCARD; + + case TCP_STATE_TIME_WAIT: + // From RFC 9293, Section 3.10.7.4. + // + // The only thing that can arrive in this state is a retransmission + // of the remote FIN. Acknowledge it, and restart the 2 MSL timeout. + + // TODO + break; + } + + if (set(segment, TCP_FLAG_URG)) { + // This is a minimal implementation, so we ignore the urgent flag + // .. Do nothing .. + TCP_DEBUG_LOG("Ignoring URG flag"); + } + + // Process the segment text + switch (c->state) { + case TCP_STATE_ESTABLISHED: + case TCP_STATE_FIN_WAIT_1: + case TCP_STATE_FIN_WAIT_2: + // From RFC 9293, Section 3.10.7.4. + // + // Once in the ESTABLISHED state, it is possible to deliver segment + // data to user RECEIVE buffers. Data from segments can be moved + // into buffers until either the buffer is full or the segment is + // empty. If the segment empties and carries a PUSH flag, then the + // user is informed, when the buffer is returned, that a PUSH has + // been received. + // + // When the TCP endpoint takes responsibility for delivering the + // data to the user, it must also acknowledge the receipt of the data. + // + // Once the TCP endpoint takes responsibility for the data, it advances + // RCV.NXT over the data accepted, and adjusts RCV.WND as appropriate + // to the current buffer availability. The total of RCV.NXT and RCV.WND + // should not be reduced. + // + // A TCP implementation MAY send an ACK segment acknowledging RCV.NXT + // when a valid segment arrives that is in the window but not at the + // left window edge (MAY-13). Please note the window management suggestions + // in Section 3.8. + // + // Send an acknowledgment of the form: + // + // + // + // This acknowledgment should be piggybacked on a segment being + // transmitted if possible without incurring undue delay. + { + size_t num = move_data_into_input_buffer(c, payload); + if (num > 0) { + if (c->cb_event) c->cb_event(c->cb_data, TCP_CONNEVENT_RECV); + transmit(c, TCP_FLAG_ACK, false); } } break; - case TCP_STATE_TIME_WAIT: - // Connection is cooling off. Ignore everything - break; - + case TCP_STATE_CLOSE_WAIT: case TCP_STATE_CLOSING: - // FIN was sent and a FIN received. Now we expect - // the ACK for our FIN. - // ..TODO.. - - change_state_to_time_wait(connection); + case TCP_STATE_LAST_ACK: + case TCP_STATE_TIME_WAIT: + // From RFC 9293, Section 3.10.7.4. + // This should not occur since a FIN has been received from the remote side. + // Ignore the segment text. break; + + default: + /* UNREACHABLE */ + DISCARD; } + + if (set(segment, TCP_FLAG_FIN)) { + // From RFC 9293, Section 3.10.7.4. + // Do not process the FIN if the state is CLOSED, LISTEN, or SYN-SENT since + // the SEG.SEQ cannot be validated; drop the segment and return. + // + // If the FIN bit is set, signal the user "connection closing" and return + // any pending RECEIVEs with same message, advance RCV.NXT over the FIN, and + // send an acknowledgment for the FIN. Note that FIN implies PUSH for any + // segment text not yet delivered to the user. + // + c->rcv_nxt++; + transmit(c, TCP_FLAG_ACK, true); + + switch (c->state) { + case TCP_STATE_SYN_RCVD: + case TCP_STATE_ESTABLISHED: + transmit(c, TCP_FLAG_FIN | TCP_FLAG_ACK, true); + if (c->cb_event) c->cb_event(c->cb_data, TCP_CONNEVENT_CLOSE); + c->state = TCP_STATE_CLOSE_WAIT; + break; + + case TCP_STATE_FIN_WAIT_1: + // From RFC 9293, Section 3.10.7.4. + // If our FIN has been ACKed (perhaps in this segment), then enter TIME-WAIT, + // start the time-wait timer, turn off the other timers; otherwise, enter the + // CLOSING state. + + if (ack == c->snd_nxt) { + // Our FIN was ACKed + transition_to_time_wait(c); + } + else + c->state = TCP_STATE_CLOSING; + break; + + case TCP_STATE_FIN_WAIT_2: + // From RFC 9293, Section 3.10.7.4. + // Enter the TIME-WAIT state. Start the time-wait timer, turn off the other + // timers. + transition_to_time_wait(c); + break; + + case TCP_STATE_TIME_WAIT: + // From RFC 9293, Section 3.10.7.4. + // Remain in the TIME-WAIT state. Restart the 2 MSL time-wait timeout. + + // TODO: Restart the TIME-WAIT timer + break; + + default: + // Possible states here are: + // - TCP_STATE_CLOSE_WAIT + // - TCP_STATE_CLOSING + // - TCP_STATE_LAST_ACK + // + // Nothing to be done! + break; + } + } + break; } } tcp_listener_t* tcp_listener_create(tcp_state_t *state, uint16_t port, bool reuse, - void *cb_data, void (*cb_ready_accept)(void*)) + void *cb_data, tcp_listeneventcb_t cb_event) { tcp_listener_t *listener; @@ -1026,7 +1452,7 @@ tcp_listener_create(tcp_state_t *state, uint16_t port, bool reuse, if (listener) { listener->closed = false; listener->cb_data = cb_data; - listener->cb_ready_accept = cb_ready_accept; + listener->cb_event = cb_event; } else { // Pop a listener connection structure from the free list @@ -1041,13 +1467,14 @@ tcp_listener_create(tcp_state_t *state, uint16_t port, bool reuse, // 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->port = port; + listener->count = 0; + listener->accepted = NULL; + listener->noestab = NULL; + listener->qhead = NULL; + listener->qtail = NULL; listener->cb_data = cb_data; - listener->cb_ready_accept = cb_ready_accept; + listener->cb_event = cb_event; // Push listener connection structure to the used list listener->prev = NULL; @@ -1062,21 +1489,22 @@ tcp_listener_create(tcp_state_t *state, uint16_t port, bool reuse, void tcp_listener_destroy(tcp_listener_t *listener) { listener->closed = true; + if (listener->count == 0) + really_close_listener(listener); } -tcp_connection_t *tcp_listener_accept(tcp_listener_t *listener, void *cb_data, - void (*cb_ready_recv)(void*), - void (*cb_ready_send)(void*)) +tcp_connection_t *tcp_listener_accept(tcp_listener_t *listener, void *cb_data, tcp_conneventcb_t cb_event) { - if (!listener->non_accepted_queue_head) + if (!listener->qhead) // Nothing to accept return NULL; + assert(listener->qtail); tcp_connection_t *c; // Pop connection from the tail of the accept queue { - c = listener->non_accepted_queue_tail; + c = listener->qtail; if (c->prev) // ->prev isn't NULL so this isn't the first element of // the queue. Therefore we need to update the ->next pointer @@ -1085,112 +1513,240 @@ tcp_connection_t *tcp_listener_accept(tcp_listener_t *listener, void *cb_data, else // ->prev is NULL so this is the first element of the queue. // We need to update the reference to the head of the list - listener->non_accepted_queue_head = NULL; + listener->qhead = NULL; assert(c->next == NULL); // This is the last element of the queue so - // it has no following node. - listener->non_accepted_queue_tail = NULL; + // it has no following node. + listener->qtail = c->prev; } // Push it to the head of the accepted list { c->prev = NULL; - c->next = listener->accepted_list; + c->next = listener->accepted; - if (listener->accepted_list) - listener->accepted_list->prev = c; - listener->accepted_list = c; + if (listener->accepted) + listener->accepted->prev = c; + listener->accepted = c; } c->cb_data = cb_data; - c->cb_ready_recv = cb_ready_recv; - c->cb_ready_send = cb_ready_send; - + c->cb_event = cb_event; return c; } -#ifdef TCP_DEBUG -static bool connection_was_accepted(tcp_connection_t *c) -{ - // Get the listener that's associated to - // the connection and iterate over it's - // accepted connections list to make sure - // that is contains the connection. - - tcp_listener_t *listener = c->listener; - - tcp_connection_t *cursor = listener->accepted_list; - - while (cursor) { - if (cursor == c) - return true; - cursor = cursor->next; - } - return false; -} -#endif - void tcp_connection_destroy(tcp_connection_t *c) { - // NOTE: This can only be called when the - // connection was accepted, so it must - // be a node of the accepted_list. - -#ifdef TCP_DEBUG - assert(connection_was_accepted(c)); -#endif - - // You can only call destroy on connections - // that were at least established - assert(c->state == TCP_STATE_ESTAB || - c->state == TCP_STATE_CLOSE_WAIT); - + // The [destroy] method implies the connection was + // returned throigh [accept], which implies it's + // at least in an ESTABLISHED state. Therefore + // the SYN-SENT and SYN-RCVD states are unreachable. switch (c->state) { - - case TCP_STATE_ESTAB: - TCP_DEBUG_LOG("ESTAB -> FIN-WAIT-1"); - emit_segment(c, TCP_FLAG_FIN | TCP_FLAG_ACK, 0, false); - c->state = TCP_STATE_FIN_WAIT_1; - break; - - case TCP_STATE_CLOSE_WAIT: - TCP_DEBUG_LOG("ESTAB -> LAST-ACK"); - emit_segment(c, TCP_FLAG_FIN | TCP_FLAG_ACK, 0, false); - c->state = TCP_STATE_LAST_ACK; - break; - default: - // This point should be unreachable + case TCP_STATE_CLOSED: + case TCP_STATE_LISTEN: + /* UNREACHABLE */ + assert(0); + break; + + case TCP_STATE_SYN_SENT: + // From RFC 9293, Section 3.10.4. + // Delete the TCB and return "error: closing" responses to + // any queued SENDs, or RECEIVEs. + assert(0); + break; + + case TCP_STATE_SYN_RCVD: + // From RFC 9293, Section 3.10.4. + // If no SENDs have been issued and there is no pending data + // to send, then form a FIN segment and send it, and enter + // FIN-WAIT-1 state; otherwise, queue for processing after + // entering ESTABLISHED state. assert(0); break; - } - // TODO: Should start a timeout here prolly + case TCP_STATE_ESTABLISHED: + // From RFC 9293, Section 3.10.4. + // Queue this until all preceding SENDs have been segmentized, + // then form a FIN segment and send it. In any case, enter + // FIN-WAIT-1 state. + c->state = TCP_STATE_FIN_WAIT_1; + transmit(c, TCP_FLAG_FIN | TCP_FLAG_ACK, true); + break; + + case TCP_STATE_FIN_WAIT_1: + case TCP_STATE_FIN_WAIT_2: + // From RFC 9293, Section 3.10.4. + // Strictly speaking, this is an error and should receive an + // "error: connection closing" response. An "ok" response would + // be acceptable, too, as long as a second FIN is not emitted + // (the first FIN may be retransmitted, though). + // + // Being in the FIN-WAIT-1 (or FIN-WAIT-2 which follows it) + // implies that the user already closed the connection, so + // calling the closing method again is an illegal use of the + // API. + assert(0); + break; + + case TCP_STATE_CLOSE_WAIT: + // From RFC 9293, Section 3.10.4. + // Queue this request until all preceding SENDs have been segmentized; + // then send a FIN segment, enter LAST-ACK state. + + transmit(c, TCP_FLAG_FIN | TCP_FLAG_ACK, true); + // When the FIN will be sent, since it was sent in the + // CLOSE-WAIT state, the state will change to LAST-ACK. + break; + + case TCP_STATE_CLOSING: + case TCP_STATE_LAST_ACK: + case TCP_STATE_TIME_WAIT: + // From RFC 9293, Section 3.10.4. + // Respond with "error: connection closing". + // + // The user tried to close the socket twice. + assert(0); + break; + + } } size_t tcp_connection_recv(tcp_connection_t *connection, void *dst, size_t len) { - slice_t s = {.ptr=dst, .len=len}; - return move_data_from_input_buffer(connection, s); + slice_t s = { .ptr=dst, .len=len }; + + size_t num; + switch (connection->state) { + + case TCP_STATE_CLOSED: + /* UNREACHABLE */ + assert(0); + break; + + case TCP_STATE_LISTEN: + case TCP_STATE_SYN_SENT: + case TCP_STATE_SYN_RCVD: + // From RFC 9293, Section 3.10.3. + // Queue for processing after entering ESTABLISHED state. If there is no + // room to queue this request, respond with "error: insufficient resources". + // + // The [recv] method can only be called after a connection is returned + // throigh [accept], which implied an ESTABLISHED state. This case isn't + // reachable. + assert(0); + break; + + case TCP_STATE_ESTABLISHED: + case TCP_STATE_FIN_WAIT_1: + case TCP_STATE_FIN_WAIT_2: + // From RFC 9293, Section 3.10.3. + // If insufficient incoming segments are queued to satisfy the request, queue + // the request. If there is no queue space to remember the RECEIVE, respond + // with "error: insufficient resources". + // + // Reassemble queued incoming segments into receive buffer and return to user. + // Mark "push seen" (PUSH) if this is the case. + // + // If RCV.UP is in advance of the data currently being passed to the user, + // notify the user of the presence of urgent data. + // + // When the TCP endpoint takes responsibility for delivering data to the user, + // that fact must be communicated to the sender via an acknowledgment. The + // formation of such an acknowledgment is described below in the discussion + // of processing an incoming segment. + num = move_from_idata(connection, s); + break; + + case TCP_STATE_CLOSE_WAIT: + // From RFC 9293, Section 3.10.3. + // Since the remote side has already sent FIN, RECEIVEs must be satisfied by + // data already on hand, but not yet delivered to the user. If no text is + // awaiting delivery, the RECEIVE will get an "error: connection closing" + // response. Otherwise, any remaining data can be used to satisfy the RECEIVE. + num = move_from_idata(connection, s); + break; + + case TCP_STATE_CLOSING: + case TCP_STATE_LAST_ACK: + case TCP_STATE_TIME_WAIT: + // From RFC 9293, Section 3.10.3. + // Return "error: connection closing". + // + // TODO: Find a way to report an error + num = 0; + break; + } + + return num; } static size_t -append_to_output_buffer(tcp_connection_t *connection, +append_to_odata(tcp_connection_t *connection, const void *src, size_t len) { - size_t capacity = TCP_OUTPUT_BUFFER_SIZE - connection->snd_wnd; + size_t capacity = TCP_OBUFFER_SIZE - connection->oused; size_t num = MIN(len, capacity); - memcpy(connection->out_buffer + connection->snd_wnd, src, num); - connection->snd_wnd += num; + memcpy(connection->odata + connection->oused, src, num); + connection->oused += num; return num; } -size_t tcp_connection_send(tcp_connection_t *connection, const void *src, size_t len) +size_t tcp_connection_send(tcp_connection_t *c, const void *src, size_t len) { - size_t num = append_to_output_buffer(connection, src, len); - emit_segment(connection, TCP_FLAG_ACK, SIZE_MAX, false); + size_t num; + switch (c->state) { + + case TCP_STATE_LISTEN: + /* UNREACHABLE */ + assert(0); + break; + + case TCP_STATE_SYN_SENT: + case TCP_STATE_SYN_RCVD: + // It should not be possible to reach this point + // since the [send] method can only be called after + // an [accept], which implies an ESTABLISHED socket. + assert(0); + break; + + case TCP_STATE_ESTABLISHED: + case TCP_STATE_CLOSE_WAIT: + // From RFC 9293, Section 3.10.2. + // Segmentize the buffer and send it with a piggybacked acknowledgment + // (acknowledgment value = RCV.NXT). If there is insufficient space to + // remember this buffer, simply return "error: insufficient resources". + // + // If the URGENT flag is set, then SND.UP <- SND.NXT and set the urgent + // pointer in the outgoing segments. + // + // (We don't support the urgent pointer) + num = append_to_odata(c, src, len); + if (num > 0) + transmit(c, TCP_FLAG_ACK, false); + break; + + case TCP_STATE_FIN_WAIT_1: + case TCP_STATE_FIN_WAIT_2: + case TCP_STATE_CLOSING: + case TCP_STATE_LAST_ACK: + case TCP_STATE_TIME_WAIT: + // From RFC 9293, Section 3.10.2. + // Return "error: connection closing" and do not service request. + num = 0; + // TODO: Report an error some way + break; + + case TCP_STATE_CLOSED: + assert(0); + break; + + default: + assert(0); + break; + } return num; } \ No newline at end of file diff --git a/src/tcp.h b/src/tcp.h index 280cb53..864a90a 100644 --- a/src/tcp.h +++ b/src/tcp.h @@ -12,8 +12,8 @@ #define TCP_MAX_TIMEOUTS 1024 #define TCP_MAX_LISTENERS 32 #define TCP_MAX_SOCKETS 1024 -#define TCP_INPUT_BUFFER_SIZE 1024 -#define TCP_OUTPUT_BUFFER_SIZE 1024 +#define TCP_IBUFFER_SIZE 1024 +#define TCP_OBUFFER_SIZE 1024 typedef struct tcp_state_t tcp_state_t; // Predeclare for cyclic references @@ -36,12 +36,29 @@ typedef struct { uint16_t checksum; uint16_t urgent_pointer; char payload[]; -} tcp_segment_t; +} __attribute__((packed)) tcp_segment_t; static_assert(sizeof(tcp_segment_t) == 20); typedef struct tcp_connection_t tcp_connection_t; typedef struct tcp_listener_t tcp_listener_t; +// After receiving a reset or close event, no +// methods shall be called on the connection +// object, not even to close it. +typedef enum { + TCP_CONNEVENT_CLOSE, + TCP_CONNEVENT_RESET, + TCP_CONNEVENT_RECV, + TCP_CONNEVENT_SEND, +} tcp_connevent_t; + +typedef enum { + TCP_LISTENEVENT_ACCEPT, +} tcp_listenevent_t; + +typedef void (*tcp_conneventcb_t)(void *data, tcp_connevent_t); +typedef void (*tcp_listeneventcb_t)(void *data, tcp_listenevent_t); + struct tcp_listener_t { tcp_state_t *state; tcp_listener_t *prev; @@ -53,20 +70,35 @@ struct tcp_listener_t { // its still holding. // In this state the listener can be reopened by setting the "closed" // flag to true (and keeping the old connections intact). + + // Port the listener is listening onto uint16_t port; - tcp_connection_t *accepted_list; - 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; + + // Number of connection + int count; + + // List of established and accepted connections + tcp_connection_t *accepted; + + // List of connections which aren't in an established + // state yet. + tcp_connection_t *noestab; + + // Queue of connections ready to be accepted + // (established but not accepted) + tcp_connection_t *qhead; + tcp_connection_t *qtail; + + tcp_listeneventcb_t cb_event; + void *cb_data; }; typedef enum { TCP_STATE_CLOSED = 0, + TCP_STATE_LISTEN, TCP_STATE_SYN_SENT, TCP_STATE_SYN_RCVD, - TCP_STATE_ESTAB, + TCP_STATE_ESTABLISHED, TCP_STATE_FIN_WAIT_1, TCP_STATE_FIN_WAIT_2, TCP_STATE_CLOSE_WAIT, @@ -76,27 +108,29 @@ typedef enum { } tcp_connstate_t; struct tcp_connection_t { + tcp_listener_t *listener; // Listener that accepted this connection tcp_connection_t *next; tcp_connection_t *prev; - void *callback_data; - void (*callback_ready_to_recv)(void*); - void (*callback_ready_to_send)(void*); + tcp_conneventcb_t cb_event; + void *cb_data; tcp_connstate_t state; ip_address_t peer_ip; // Network byte order uint16_t peer_port; // CPU byte order - uint64_t estimated_rtt; - uint64_t estimated_dev; - - bool calculating_rtt; - uint32_t rtt_calc_seq; - uint64_t rtt_calc_time; + // From RFC 6298, Section 2 + // To compute the current RTO, a TCP sender maintains two state + // variables, SRTT (smoothed round-trip time) and RTTVAR (round-trip + // time variation). In addition, we assume a clock granularity of G + // seconds. + float srtt; + float rttvar; tcp_timer_t *retr_timer; + tcp_timer_t *wait_timer; // Send Sequence Space // @@ -149,10 +183,24 @@ struct tcp_connection_t { // It's the sequence number of the last // byte sent and acknowledged by the peer. - bool in_buffer_syn; - bool in_buffer_fin; - char in_buffer[TCP_INPUT_BUFFER_SIZE]; - char out_buffer[TCP_OUTPUT_BUFFER_SIZE]; + uint32_t snd_wl1; // SND.WL1 from RFC 9293 + // segment sequence number used for last + // window update. + + uint32_t snd_wl2; // SND.WL2 from RFC 9293 + // segment acknowledgment number used for + // last window update. + + uint32_t last_acked; // Last sequence number of the peer that was ACKed + + // If true, the next segment which will empty the + // output buffer will contain a FIN. + bool send_fin_when_fully_flushed; + bool waiting_ack_for_syn; + bool waiting_ack_for_fin; + size_t oused; + char idata[TCP_IBUFFER_SIZE]; + char odata[TCP_OBUFFER_SIZE]; }; typedef struct { @@ -180,10 +228,9 @@ void tcp_init(tcp_state_t *tcp_state, ip_address_t ip, tcp_callback void tcp_free(tcp_state_t *tcp_state); void tcp_ms_passed(tcp_state_t *state, size_t ms); 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, bool reuse, void *data, void (*callback)(void*)); +tcp_listener_t *tcp_listener_create(tcp_state_t *state, uint16_t port, bool reuse, void *cb_data, tcp_listeneventcb_t func); 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*)); +tcp_connection_t *tcp_listener_accept(tcp_listener_t *listener, void *cb_data, tcp_conneventcb_t func); void tcp_connection_destroy(tcp_connection_t *connection); -void tcp_connection_finish(tcp_connection_t *connection); size_t tcp_connection_recv(tcp_connection_t *connection, void *dst, size_t len); size_t tcp_connection_send(tcp_connection_t *connection, const void *src, size_t len); diff --git a/src/tcp_timer.c b/src/tcp_timer.c index 0087402..f0d9db7 100644 --- a/src/tcp_timer.c +++ b/src/tcp_timer.c @@ -61,6 +61,8 @@ tcp_timer_t *tcp_timer_create(tcp_timerset_t *set, size_t ms, // first item of the free list. timer->set = set; + timer->set_time = set->current_time_ms; + timer->trg_time = ms; timer->deadline = set->current_time_ms + ms; timer->data = data; timer->callback = callback; @@ -125,8 +127,8 @@ void tcp_timerset_step(tcp_timerset_t *set, size_t ms) // Scan through all of the timeouts that just triggered tcp_timer_t *timeout = set->used_list; - while (timeout) { - + do { + if (timeout->deadline > set->current_time_ms) // This timeout didn't trigger, so the last // timed out timeout was the previous one. @@ -137,7 +139,7 @@ void tcp_timerset_step(tcp_timerset_t *set, size_t ms) timedout_tail = timeout; timeout = timeout->next; - } + } while (timeout); // Now put the list of timed out timeouts back // into the free list diff --git a/src/tcp_timer.h b/src/tcp_timer.h index 7b4741b..f8407a6 100644 --- a/src/tcp_timer.h +++ b/src/tcp_timer.h @@ -14,6 +14,8 @@ struct tcp_timer_t { tcp_timerset_t *set; tcp_timer_t *prev; tcp_timer_t *next; + uint64_t set_time; + uint64_t trg_time; uint64_t deadline; void (*callback)(void *data); void *data; diff --git a/src/utils.c b/src/utils.c index cae0940..7e9d271 100644 --- a/src/utils.c +++ b/src/utils.c @@ -17,15 +17,14 @@ static bool is_hex_digit(char c) static int int_from_hex_digit(char c) { assert(is_hex_digit(c)); - if (c >= 'A' || c <= 'F') + if (c >= 'A' && c <= 'F') return c - 'A' + 10; - if (c >= 'a' || c <= 'f') + if (c >= 'a' && c <= 'f') return c - 'a' + 10; return c - '0'; } -bool parse_mac(const char *src, size_t len, - mac_address_t *mac) +bool parse_mac(const char *src, size_t len, mac_address_t *mac) { if (src == NULL || len != 17 || !is_hex_digit(src[0]) @@ -49,20 +48,13 @@ bool parse_mac(const char *src, size_t len, 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])]; - } + if (mac) + for (int i = 0; i < 6; i++) { + int u = int_from_hex_digit(src[i * 3 + 0]); + int v = int_from_hex_digit(src[i * 3 + 1]); + mac->data[i] = (max_char_map[u] << 4) | max_char_map[v]; + } + return true; }