first commit
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
*.pcapng
|
||||||
|
loop
|
||||||
|
test_arp
|
||||||
|
test_arp_cov
|
||||||
|
report_arp_cov
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
New-NetFirewallRule -DisplayName "WSL" -Direction Inbound -InterfaceAlias "vEthernet (WSL)" -Action Allow
|
||||||
|
|
||||||
|
ip link add br0 type bridge
|
||||||
|
|
||||||
|
ip link set tap0 master br0
|
||||||
|
ip link set dev eth0 down
|
||||||
|
ip addr flush dev eth0
|
||||||
|
ip link set dev eth0 up
|
||||||
|
ip link set eth0 master br0
|
||||||
|
|
||||||
|
ip link set dev br0 up
|
||||||
|
|
||||||
|
sudo iptables -A FORWARD --in-interface enp5s0 --out-interface tun0 -j ACCEPT
|
||||||
|
sudo iptables -t nat -A POSTROUTING --out-interface tun0 -j MASQUERADE
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#include <microtcp.h>
|
||||||
|
|
||||||
|
typedef struct microhttp_server_t http_server_t;
|
||||||
|
http_server_t *microhttp_server_create(microtcp_t *mtcp, uint16_t port);
|
||||||
|
void microhttp_server_destroy(http_server_t *server);
|
||||||
|
void microhttp_server_serve(microhttp_server_t *server, void *data, void (*callback)(void*, microhttp_request_t*));
|
||||||
|
|
||||||
|
struct microhttp_server_t {
|
||||||
|
microtcp_t *tcp;
|
||||||
|
microtcp_listener_t *listener;
|
||||||
|
};
|
||||||
|
|
||||||
|
microhttp_server_t *microhttp_server_create(microtcp_t *mtcp, uint16_t port)
|
||||||
|
{
|
||||||
|
microhttp_server_t *server = malloc(sizeof(microhttp_server_t));
|
||||||
|
if (!server)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
microtcp_listener_t *listener = microtcp_listener_create(mtcp, port);
|
||||||
|
if (!listener) {
|
||||||
|
free(server);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
server->mtcp = mtcp;
|
||||||
|
server->listener = listener;
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
void microhttp_server_serve(microhttp_server_t *server, void *data, void (*callback)(void*, microhttp_request_t*))
|
||||||
|
{
|
||||||
|
char buffer[65536];
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
|
||||||
|
microtcp_socket_t *socket = microtcp_listener_accept(server->listener);
|
||||||
|
if (!socket)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int num = microtcp_socket_recv(socket, buffer, sizeof(buffer));
|
||||||
|
if (num >= 0) {
|
||||||
|
hp_error_t error;
|
||||||
|
hp_request_t request;
|
||||||
|
if (!hp_parse(buffer, num, &request, &error)) {
|
||||||
|
..
|
||||||
|
} else {
|
||||||
|
..
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
microtcp_socket_destroy(socket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void microhttp_server_destroy(microhttp_server_t *server)
|
||||||
|
{
|
||||||
|
microtcp_listener_destroy(server->listener);
|
||||||
|
free(server);
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#include "net.h"
|
||||||
|
|
||||||
|
static size_t send_callback()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int tun_fd;
|
||||||
|
|
||||||
|
static size_t recv_callback(void *context, void *dst, size_t len)
|
||||||
|
{
|
||||||
|
return read(tun_fd, dst, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
net_t net;
|
||||||
|
net_init(&net, ip, mac, NULL, send_callback, recv_callback);
|
||||||
|
net_spawn_thread(&net);
|
||||||
|
|
||||||
|
uint16_t port = 8080;
|
||||||
|
|
||||||
|
net_listener_t *listener = net_listener_create(&net, port);
|
||||||
|
if (listener == NULL) {
|
||||||
|
fprintf(stderr, "Failed to start listening\n");
|
||||||
|
net_free(&net);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
|
||||||
|
net_socket_t *client = net_listener_accept(listener);
|
||||||
|
|
||||||
|
if (client == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
char message[1024];
|
||||||
|
size_t messlen;
|
||||||
|
|
||||||
|
messlen = net_socket_recv(client, message, sizeof(message));
|
||||||
|
net_socket_send(client, "echo: ", sizeof("echo: "));
|
||||||
|
net_socket_send(client, message, messlen);
|
||||||
|
|
||||||
|
net_socket_destroy(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
net_listener_destroy(listener);
|
||||||
|
net_free(&net);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#include "mutcp.h"
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
mutcp_t state;
|
||||||
|
mutcp_create(&state);
|
||||||
|
|
||||||
|
mutcp_listener_socket_t *listener =
|
||||||
|
mutcp_listener_socket_create(&state, 8080);
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
mutcp_socket_t *socket =
|
||||||
|
mutcp_listener_socket_accept(listener);
|
||||||
|
|
||||||
|
char buffer[1024];
|
||||||
|
size_t num = mutcp_socket_read(socket, buffer, sizeof(buffer));
|
||||||
|
mutcp_socket_write(socket, "echo: ", 6);
|
||||||
|
mutcp_socket_write(socket, buffer, num);
|
||||||
|
|
||||||
|
mutcp_socket_destroy(socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
mutcp_listener_socket_destroy(listener);
|
||||||
|
mutcp_destroy(&state);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
typedef struct microtcp_t microtcp_t;
|
||||||
|
typedef struct microtcp_socket_t microtcp_socket_t;
|
||||||
|
|
||||||
|
#define MICROTCP_MAX_BUFFERS 8
|
||||||
|
#define MICROTCP_MAX_SOCKETS 32
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t data[6];
|
||||||
|
} microtcp_mac_t;
|
||||||
|
|
||||||
|
typedef uint32_t microtcp_ip_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
|
||||||
|
MICROTCP_ERRCODE_NONE = 0,
|
||||||
|
|
||||||
|
// Returned by microtcp_open and microtcp_accept
|
||||||
|
MICROTCP_ERRCODE_SOCKETLIMIT,
|
||||||
|
|
||||||
|
// Returned by microtcp_open
|
||||||
|
MICROTCP_ERRCODE_TCPERROR,
|
||||||
|
MICROTCP_ERRCODE_BADCONDVAR,
|
||||||
|
|
||||||
|
// Returned by microtcp_accept
|
||||||
|
MICROTCP_ERRCODE_NOTLISTENER,
|
||||||
|
MICROTCP_ERRCODE_CANTBLOCK,
|
||||||
|
MICROTCP_ERRCODE_NOTHINGTOACCEPT,
|
||||||
|
|
||||||
|
// Returned by microtcp_recv and microtcp_send
|
||||||
|
MICROTCP_ERRCODE_NOTCONNECTION,
|
||||||
|
|
||||||
|
} microtcp_errcode_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
void *data;
|
||||||
|
void (*free)(void *data);
|
||||||
|
int (*send)(void *data, const void *src, size_t len);
|
||||||
|
int (*recv)(void *data, void *dst, size_t len);
|
||||||
|
} microtcp_callbacks_t;
|
||||||
|
|
||||||
|
microtcp_t *microtcp_create();
|
||||||
|
microtcp_t *microtcp_create_using_callbacks(microtcp_ip_t ip, microtcp_mac_t mac, microtcp_callbacks_t callbacks);
|
||||||
|
void microtcp_destroy(microtcp_t *mtcp);
|
||||||
|
const char *microtcp_strerror(microtcp_errcode_t errcode);
|
||||||
|
microtcp_socket_t *microtcp_open(microtcp_t *mtcp, uint16_t port, microtcp_errcode_t *errcode);
|
||||||
|
microtcp_socket_t *microtcp_accept(microtcp_socket_t *socket, bool no_block, microtcp_errcode_t *errcode);
|
||||||
|
void microtcp_close(microtcp_socket_t *socket);
|
||||||
|
size_t microtcp_send(microtcp_socket_t *socket, const void *src, size_t len, microtcp_errcode_t *errcode);
|
||||||
|
size_t microtcp_recv(microtcp_socket_t *socket, void *dst, size_t len, microtcp_errcode_t *errcode);
|
||||||
|
void microtcp_step(microtcp_t *mtcp);
|
||||||
|
void microtcp_process_packet(microtcp_t *mtcp, const void *packet, size_t len);
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
all: loop test_net test_arp report_arp_cov
|
||||||
|
|
||||||
|
loop:
|
||||||
|
gcc src/arp.c src/ip.c src/icmp.c src/tcp.c src/microtcp.c src/microtcp_linux.c test/loop.c -o loop -Wall -Wextra -g -DARP_DEBUG -DMICROTCP_DEBUG -DIP_DEBUG -DICMP_DEBUG -DTCP_DEBUG -DMICROTCP_BACKGROUND_THREAD -DMICROTCP_LINUX -pthread -Iinclude/ -fsanitize=thread
|
||||||
|
|
||||||
|
test_arp:
|
||||||
|
gcc src/arp.c test/test_arp.c test/test_arp_util.c -o test_arp -Wall -Wextra -g -Iinclude/
|
||||||
|
|
||||||
|
test_arp_cov:
|
||||||
|
gcc src/arp.c test/test_arp.c test/test_arp_util.c -o test_arp_cov -Wall -Wextra -g -fprofile-arcs -ftest-coverage -lgcov
|
||||||
|
|
||||||
|
report_arp_cov: test_arp_cov
|
||||||
|
./test_arp_cov
|
||||||
|
lcov --capture --directory . --output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
genhtml coverage.info --output-directory report_arp_cov --rc lcov_branch_coverage=1
|
||||||
|
rm *.gcda *.gcno coverage.info
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f *.gcda *.gcno coverage.info
|
||||||
|
rm -f test_arp test_arp_cov
|
||||||
|
rm -f loop
|
||||||
|
rm -rf report_arp_cov
|
||||||
@@ -0,0 +1,734 @@
|
|||||||
|
/* Il protocollo ARP (Address Resolution Protocol)
|
||||||
|
* permette di tradurre gli indirizzi di livello
|
||||||
|
* rete (come IP) a quelli del livello inferiore
|
||||||
|
* data-link (come ethernet).
|
||||||
|
* Per grandi linee, i messaggi ARP possono essere
|
||||||
|
* REQUEST o REPLY. Quando un host vuole comunicare
|
||||||
|
* con un altro host dato il suo IP (o qualsiasi
|
||||||
|
* indirizzo di livello 3), manda un messaggio di
|
||||||
|
* REQUEST in broadcast (MAC di destinazione
|
||||||
|
* ff:ff:ff:ff:ff:ff) contenente l'indirizzo di
|
||||||
|
* livello 3 del quale si vuole conoscere quello di
|
||||||
|
* livello 2. Ciascun host della rete riceve il
|
||||||
|
* messaggio e controlla se la richiesta è relativa
|
||||||
|
* al proprio IP. Se il controllo risulta positivo
|
||||||
|
* risponde con un messaggio di REPLY contenente
|
||||||
|
* il proprio MAC. Il messaggio di REPLY, a differenza
|
||||||
|
* della REQUEST è un unicast dato che è noto il
|
||||||
|
* destinatario.
|
||||||
|
*
|
||||||
|
* Il messaggio ARP è grande 28 byte ed, indipendentemente
|
||||||
|
* dal tipo di richiesta, ha questa struttura:
|
||||||
|
*
|
||||||
|
* (16 bits per row)
|
||||||
|
* +-----------------------------------+
|
||||||
|
* 0 | hardware_type |
|
||||||
|
* +-----------------------------------+
|
||||||
|
* 2 | protocol_type |
|
||||||
|
* +-----------------+-----------------+
|
||||||
|
* 4 | hardware_length | protocol_length |
|
||||||
|
* +-----------------+-----------------+
|
||||||
|
* 6 | operation_type |
|
||||||
|
* +-----------------------------------+
|
||||||
|
* 8 | sender_hardware_address |
|
||||||
|
* +- - - - - - - - - - - - - - - - - -+
|
||||||
|
* | |
|
||||||
|
* +- - - - - - - - - - - - - - - - - -+
|
||||||
|
* | |
|
||||||
|
* +-----------------------------------+
|
||||||
|
* 14 | sender_protocol_address |
|
||||||
|
* +- - - - - - - - - - - - - - - - - -+
|
||||||
|
* | |
|
||||||
|
* +-----------------------------------+
|
||||||
|
* 18 | target_hardware_address |
|
||||||
|
* +- - - - - - - - - - - - - - - - - -+
|
||||||
|
* | |
|
||||||
|
* +- - - - - - - - - - - - - - - - - -+
|
||||||
|
* | |
|
||||||
|
* +-----------------------------------+
|
||||||
|
* 24 | target_protocol_address |
|
||||||
|
* +- - - - - - - - - - - - - - - - - -+
|
||||||
|
* | |
|
||||||
|
* +-----------------------------------+
|
||||||
|
*
|
||||||
|
* I campi hardware_type e protocol_type indicano
|
||||||
|
* il protocollo di livello 2 e quello di livello 3.
|
||||||
|
* Nel caso di IP su Ethernet si ha hardware_type=1
|
||||||
|
* e protocol_type=0x800.
|
||||||
|
*
|
||||||
|
* I campi hardware_length e protocol_length
|
||||||
|
* indicano la dimensione in byte degli indirizzi
|
||||||
|
* dei due protocolli. Per IP e Ethernet questi
|
||||||
|
* sono ridondanti dato che ogni indirizzo Ethernet
|
||||||
|
* è di 6 byte ed ogni indirizzo IP di 4.
|
||||||
|
*
|
||||||
|
* Il campo operation_type indica la finalità del
|
||||||
|
* messaggio. Può essere uno di:
|
||||||
|
* ARP REQUEST -> operation_type=1
|
||||||
|
* ARP REPLY -> operation_type=2
|
||||||
|
* RARP REQUEST -> operation_type=3
|
||||||
|
* RARP REPLY -> operation_type=4
|
||||||
|
* (possiamo ignorare RARP per ora)
|
||||||
|
*
|
||||||
|
* Il campo sender_hardware_address e sender_protocol_address
|
||||||
|
* contengono MAC e IP di chi ha inviato il messaggio.
|
||||||
|
*
|
||||||
|
* Il campo target_hardware_address cambia di significato
|
||||||
|
* a seconda del tipo di operazione:
|
||||||
|
* ARP REQUEST -> è vuoto, perchè non è noto il MAC di
|
||||||
|
* destinazione (vogliamo determinarlo)
|
||||||
|
* ARP REPLY -> indirizzo MAC di chi ha fatto la REQUEST
|
||||||
|
*
|
||||||
|
* Il campo target_protocol_address contiene l'indirizzo IP
|
||||||
|
* di chi ha inviato il messaggio.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
struct iphdr
|
||||||
|
{
|
||||||
|
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||||
|
unsigned int ihl:4;
|
||||||
|
unsigned int version:4;
|
||||||
|
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||||
|
unsigned int version:4;
|
||||||
|
unsigned int ihl:4;
|
||||||
|
#else
|
||||||
|
# error "Please fix <bits/endian.h>"
|
||||||
|
#endif
|
||||||
|
uint8_t tos;
|
||||||
|
uint16_t tot_len;
|
||||||
|
uint16_t id;
|
||||||
|
uint16_t frag_off;
|
||||||
|
uint8_t ttl;
|
||||||
|
uint8_t protocol;
|
||||||
|
uint16_t check;
|
||||||
|
uint32_t saddr;
|
||||||
|
uint32_t daddr;
|
||||||
|
// The options start here.
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ethhdr {
|
||||||
|
unsigned char h_dest[ETH_ALEN]; // destination eth addr
|
||||||
|
unsigned char h_source[ETH_ALEN]; // source ether addr
|
||||||
|
__be16 h_proto; // packet type ID field
|
||||||
|
} __attribute__((packed));
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include "arp.h"
|
||||||
|
|
||||||
|
#ifdef ARP_DEBUG
|
||||||
|
#include <stdio.h>
|
||||||
|
#define ARP_DEBUG_LOG(fmt, ...) fprintf(stderr, "ARP :: " fmt "\n", ## __VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define ARP_DEBUG_LOG(...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ARP_HARDWARE_ETHERNET = 1,
|
||||||
|
} arp_hardware_type;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ARP_PROTOCOL_IP = 0x800,
|
||||||
|
} arp_protocol_type;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ARP_OPERATION_REQUEST = 1,
|
||||||
|
ARP_OPERATION_REPLY = 2,
|
||||||
|
} arp_operation_t;
|
||||||
|
|
||||||
|
void arp_change_output_buffer(arp_state_t *state, void *ptr, size_t max)
|
||||||
|
{
|
||||||
|
if (max < sizeof(arp_packet_t))
|
||||||
|
state->output = NULL;
|
||||||
|
else
|
||||||
|
state->output = ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void arp_translation_table_seconds_passed(arp_translation_table_t *table, size_t seconds)
|
||||||
|
{
|
||||||
|
table->time += seconds;
|
||||||
|
|
||||||
|
// Determine all of the elements of the table that have just
|
||||||
|
// timed out.
|
||||||
|
//
|
||||||
|
// The [used_list] contains all of the active table entries
|
||||||
|
// in a doubly linked list. The first element is referred by
|
||||||
|
// [table->used_list_head], and the last by [table->used_list_tail].
|
||||||
|
// The entries are ordered in descending [entry->timeout]
|
||||||
|
// attribute. The [timeout] attribute indicates the absolute
|
||||||
|
// time at which the entry will be considered invalid,
|
||||||
|
// relative to [table->time].
|
||||||
|
//
|
||||||
|
// Since the list goes from high to low timeout, if an entry
|
||||||
|
// at a given point in time isn't timed-out, all of the
|
||||||
|
// entries that come before it also aren't timed-out.
|
||||||
|
// Analogously, is an entry in a given point in time is
|
||||||
|
// timed-out, all of the entries after it are also timed-out.
|
||||||
|
//
|
||||||
|
// In general, at any given point in time, the list is made
|
||||||
|
// of a first half of non-timed-out entries and a second half
|
||||||
|
// of timed-out entries.
|
||||||
|
//
|
||||||
|
// This function needs to remove the timed-out tail of the
|
||||||
|
// used entries list and add it to the free entry list.
|
||||||
|
//
|
||||||
|
|
||||||
|
// Find from the end of the list the first non-timed-out
|
||||||
|
// entry. The timed-out elements will be all of the ones
|
||||||
|
// that come after it.
|
||||||
|
//
|
||||||
|
// NOTE: If all of the entries are timed-out or the list is
|
||||||
|
// empty, the loop will exit with the NULL entry.
|
||||||
|
|
||||||
|
arp_translation_table_entry_t *entry = table->used_list_tail;
|
||||||
|
while (entry && entry->timeout < table->time)
|
||||||
|
entry = entry->prev;
|
||||||
|
|
||||||
|
// First and last element of the timed-out list. We need
|
||||||
|
// to determine these.
|
||||||
|
arp_translation_table_entry_t *timeout_list;
|
||||||
|
arp_translation_table_entry_t *timeout_tail;
|
||||||
|
|
||||||
|
if (entry) {
|
||||||
|
|
||||||
|
// The iteration didn't end with a NULL cursor, so either
|
||||||
|
// there are no timed-out elements (in which case the cursor
|
||||||
|
// is the tail of the list) or there are both timed-out and
|
||||||
|
// non-timed-out entries.
|
||||||
|
//
|
||||||
|
// Either way, the start of the list is [entry->next].
|
||||||
|
timeout_list = entry->next;
|
||||||
|
//
|
||||||
|
// If there are no timed-out entries, the tail of the timed-out
|
||||||
|
// list must be NULL, else it's the tail of the used list.
|
||||||
|
timeout_tail = entry->next ? table->used_list_tail : NULL;
|
||||||
|
//
|
||||||
|
// The entry becomes the new tail
|
||||||
|
entry->next = NULL;
|
||||||
|
table->used_list_tail = entry;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// If the iteration ended with a NULL cursor, there
|
||||||
|
// are no valid entries in the list. Either the list
|
||||||
|
// is all timed-out, or it's empty.
|
||||||
|
//
|
||||||
|
// Either way we take the list pointers and make them
|
||||||
|
// out timed-out list.
|
||||||
|
timeout_list = table->used_list_head;
|
||||||
|
timeout_tail = table->used_list_tail;
|
||||||
|
//
|
||||||
|
// If the list wasn't empty, we make it so.
|
||||||
|
table->used_list_head = NULL;
|
||||||
|
table->used_list_tail = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the timed-out list to the free list
|
||||||
|
if (timeout_list) {
|
||||||
|
timeout_list->prev = NULL;
|
||||||
|
timeout_tail->next = table->free_list;
|
||||||
|
table->free_list = timeout_list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void arp_seconds_passed(arp_state_t *state, size_t seconds)
|
||||||
|
{
|
||||||
|
state->time += seconds;
|
||||||
|
|
||||||
|
// Scan through all of the timed-out entries
|
||||||
|
// in the pending request list from the tail
|
||||||
|
arp_pending_request_t *cursor = state->pending_request_used_tail;
|
||||||
|
while (cursor && cursor->timeout < state->time)
|
||||||
|
cursor = cursor->prev;
|
||||||
|
|
||||||
|
|
||||||
|
// Chop off the list of timed out entries
|
||||||
|
arp_pending_request_t *timeout_list;
|
||||||
|
arp_pending_request_t *timeout_tail;
|
||||||
|
|
||||||
|
if (cursor) {
|
||||||
|
// Cursor holds the first request that's not timed out,
|
||||||
|
// therefore all of the entries that come after it are
|
||||||
|
// now invalid
|
||||||
|
timeout_list = cursor->next;
|
||||||
|
timeout_tail = cursor->next ? state->pending_request_used_tail : NULL;
|
||||||
|
|
||||||
|
// Now chop off the list
|
||||||
|
cursor->next = NULL;
|
||||||
|
state->pending_request_used_tail = cursor;
|
||||||
|
} else {
|
||||||
|
// Either the list is empty or all of the requests are
|
||||||
|
// now invalid.
|
||||||
|
timeout_list = state->pending_request_used_list;
|
||||||
|
timeout_tail = state->pending_request_used_tail;
|
||||||
|
state->pending_request_used_list = NULL;
|
||||||
|
state->pending_request_used_tail = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now walk through the timed out entries and
|
||||||
|
// run the callback with the timeout status code
|
||||||
|
arp_pending_request_t *timeout_cursor = timeout_list;
|
||||||
|
while (timeout_cursor) {
|
||||||
|
timeout_cursor->callback(timeout_cursor->callback_data, ARP_RESOLUTION_TIMEOUT, MAC_ZERO);
|
||||||
|
timeout_cursor = timeout_cursor->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now put the timed out entries back in the free
|
||||||
|
// list (if there are any)
|
||||||
|
if (timeout_list) {
|
||||||
|
timeout_list->prev = NULL;
|
||||||
|
timeout_tail->next = state->pending_request_free_list;
|
||||||
|
state->pending_request_free_list = timeout_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
arp_translation_table_seconds_passed(&state->table, seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
arp_translation_table_init(arp_translation_table_t *table)
|
||||||
|
{
|
||||||
|
table->time = 0;
|
||||||
|
table->used_list_head = NULL;
|
||||||
|
table->used_list_tail = NULL;
|
||||||
|
table->free_list = table->entries;
|
||||||
|
for (size_t i = 0; i < ARP_TRANSLATION_TABLE_SIZE-1; i++) {
|
||||||
|
table->entries[i].prev = NULL;
|
||||||
|
table->entries[i].next = table->entries + i+1;
|
||||||
|
}
|
||||||
|
table->entries[ARP_TRANSLATION_TABLE_SIZE-1].prev = NULL;
|
||||||
|
table->entries[ARP_TRANSLATION_TABLE_SIZE-1].next = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
arp_translation_table_free(arp_translation_table_t *table)
|
||||||
|
{
|
||||||
|
(void) table;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef ARP_DEBUG
|
||||||
|
static bool
|
||||||
|
arp_translation_table_entry_is_used(arp_translation_table_t *table,
|
||||||
|
arp_translation_table_entry_t *entry)
|
||||||
|
{
|
||||||
|
arp_translation_table_entry_t *cursor = table->used_list_head;
|
||||||
|
while (cursor) {
|
||||||
|
if (cursor == entry)
|
||||||
|
return true;
|
||||||
|
cursor = cursor->next;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
arp_translation_table_entry_is_unlinked(arp_translation_table_t *table,
|
||||||
|
arp_translation_table_entry_t *entry)
|
||||||
|
{
|
||||||
|
return entry->prev == NULL
|
||||||
|
&& entry->next == NULL
|
||||||
|
&& table->free_list != entry
|
||||||
|
&& table->used_list_head != entry
|
||||||
|
&& table->used_list_tail != entry;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void
|
||||||
|
arp_translation_table_unlink_used_entry(arp_translation_table_t *table,
|
||||||
|
arp_translation_table_entry_t *entry)
|
||||||
|
{
|
||||||
|
|
||||||
|
#ifdef ARP_DEBUG
|
||||||
|
assert(!arp_translation_table_entry_is_unlinked(table, entry));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (entry->prev)
|
||||||
|
entry->prev->next = entry->next;
|
||||||
|
else
|
||||||
|
table->used_list_head = entry->next;
|
||||||
|
|
||||||
|
if (entry->next)
|
||||||
|
entry->next->prev = entry->prev;
|
||||||
|
else
|
||||||
|
table->used_list_tail = entry->prev;
|
||||||
|
|
||||||
|
entry->prev = NULL;
|
||||||
|
entry->next = NULL;
|
||||||
|
|
||||||
|
#ifdef ARP_DEBUG
|
||||||
|
assert(arp_translation_table_entry_is_unlinked(table, entry));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
arp_translation_table_insert_unlinked_entry_into_used_list(arp_translation_table_t *table,
|
||||||
|
arp_translation_table_entry_t *entry)
|
||||||
|
{
|
||||||
|
|
||||||
|
#ifdef ARP_DEBUG
|
||||||
|
assert(arp_translation_table_entry_is_unlinked(table, entry));
|
||||||
|
assert(!arp_translation_table_entry_is_used(table, entry));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Find the first entry with the lower timeout
|
||||||
|
arp_translation_table_entry_t *cursor = table->used_list_head;
|
||||||
|
while (cursor && cursor->timeout < entry->timeout)
|
||||||
|
cursor = cursor->next;
|
||||||
|
|
||||||
|
if (cursor) {
|
||||||
|
// Insert the entry before the cursor position.
|
||||||
|
entry->prev = cursor->prev;
|
||||||
|
entry->next = cursor;
|
||||||
|
|
||||||
|
if (cursor->prev)
|
||||||
|
cursor->prev->next = entry;
|
||||||
|
else
|
||||||
|
table->used_list_head = entry;
|
||||||
|
|
||||||
|
cursor->prev = entry;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Either the list is empty or the entry must
|
||||||
|
// be inserted last.
|
||||||
|
|
||||||
|
entry->prev = table->used_list_tail;
|
||||||
|
entry->next = NULL;
|
||||||
|
|
||||||
|
if (table->used_list_tail)
|
||||||
|
table->used_list_tail->next = entry;
|
||||||
|
else
|
||||||
|
table->used_list_head = entry;
|
||||||
|
table->used_list_tail = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ARP_DEBUG
|
||||||
|
assert(!arp_translation_table_entry_is_unlinked(table, entry));
|
||||||
|
assert(arp_translation_table_entry_is_used(table, entry));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
arp_translation_table_free_least_recently_used_entry(arp_translation_table_t *table)
|
||||||
|
{
|
||||||
|
arp_translation_table_entry_t *entry = table->used_list_tail;
|
||||||
|
if (entry) {
|
||||||
|
|
||||||
|
#ifdef ARP_DEBUG
|
||||||
|
assert(!arp_translation_table_entry_is_unlinked(table, entry));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
arp_translation_table_unlink_used_entry(table, entry);
|
||||||
|
|
||||||
|
#ifdef ARP_DEBUG
|
||||||
|
assert(arp_translation_table_entry_is_unlinked(table, entry));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Push the entry to the free list
|
||||||
|
entry->next = table->free_list;
|
||||||
|
table->free_list = entry;
|
||||||
|
|
||||||
|
#ifdef ARP_DEBUG
|
||||||
|
assert(!arp_translation_table_entry_is_unlinked(table, entry));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static arp_translation_table_entry_t*
|
||||||
|
arp_translation_table_find_entry_by_ip(arp_translation_table_t *table,
|
||||||
|
ip_address_t ip)
|
||||||
|
{
|
||||||
|
arp_translation_table_entry_t *entry = table->used_list_head;
|
||||||
|
while (entry) {
|
||||||
|
if (entry->ip == ip)
|
||||||
|
return entry;
|
||||||
|
entry = entry->next;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool arp_translation_table_find_mac_by_ip(arp_translation_table_t *table,
|
||||||
|
ip_address_t ip, mac_address_t *mac)
|
||||||
|
{
|
||||||
|
arp_translation_table_entry_t *entry =
|
||||||
|
arp_translation_table_find_entry_by_ip(table, ip);
|
||||||
|
|
||||||
|
if (entry)
|
||||||
|
*mac = entry->mac;
|
||||||
|
return !!entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
static arp_translation_table_entry_t*
|
||||||
|
arp_translation_table_pop_free_entry(arp_translation_table_t *table)
|
||||||
|
{
|
||||||
|
arp_translation_table_entry_t *entry = table->free_list;
|
||||||
|
if (entry)
|
||||||
|
table->free_list = entry->next;
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
arp_translation_table_initialize_entry(arp_translation_table_entry_t *entry,
|
||||||
|
mac_address_t mac, ip_address_t ip,
|
||||||
|
uint64_t timeout)
|
||||||
|
{
|
||||||
|
entry->mac = mac;
|
||||||
|
entry->ip = ip;
|
||||||
|
entry->timeout = timeout;
|
||||||
|
entry->prev = NULL;
|
||||||
|
entry->next = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
arp_translation_table_insert_or_update(arp_translation_table_t *table,
|
||||||
|
mac_address_t mac, ip_address_t ip,
|
||||||
|
uint64_t timeout)
|
||||||
|
{
|
||||||
|
arp_translation_table_entry_t *entry =
|
||||||
|
arp_translation_table_find_entry_by_ip(table, ip);
|
||||||
|
|
||||||
|
if (entry) {
|
||||||
|
entry->timeout = table->time + timeout; // Refresh timeout
|
||||||
|
arp_translation_table_unlink_used_entry(table, entry);
|
||||||
|
} else {
|
||||||
|
entry = arp_translation_table_pop_free_entry(table);
|
||||||
|
if (!entry) {
|
||||||
|
arp_translation_table_free_least_recently_used_entry(table);
|
||||||
|
entry = arp_translation_table_pop_free_entry(table);
|
||||||
|
}
|
||||||
|
assert(entry);
|
||||||
|
|
||||||
|
arp_translation_table_initialize_entry(entry, mac, ip, table->time + timeout);
|
||||||
|
}
|
||||||
|
arp_translation_table_insert_unlinked_entry_into_used_list(table, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
arp_translation_table_update(arp_translation_table_t *table,
|
||||||
|
mac_address_t mac, ip_address_t ip,
|
||||||
|
uint64_t timeout)
|
||||||
|
{
|
||||||
|
arp_translation_table_entry_t *entry =
|
||||||
|
arp_translation_table_find_entry_by_ip(table, ip);
|
||||||
|
|
||||||
|
if (entry) {
|
||||||
|
arp_translation_table_unlink_used_entry(table, entry);
|
||||||
|
arp_translation_table_initialize_entry(entry, mac, ip, table->time + timeout);
|
||||||
|
arp_translation_table_insert_unlinked_entry_into_used_list(table, entry);
|
||||||
|
}
|
||||||
|
return !!entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
void arp_init(arp_state_t *state, ip_address_t ip, mac_address_t mac,
|
||||||
|
void *send_data, void (*send)(void*, mac_address_t))
|
||||||
|
{
|
||||||
|
state->time = 0;
|
||||||
|
state->request_timeout = 1;
|
||||||
|
state->cache_timeout = 10;
|
||||||
|
state->output = NULL;
|
||||||
|
state->send_data = send_data;
|
||||||
|
state->send = send;
|
||||||
|
|
||||||
|
state->self_ip = ip;
|
||||||
|
state->self_mac = mac;
|
||||||
|
arp_translation_table_init(&state->table);
|
||||||
|
|
||||||
|
state->pending_request_used_list = NULL;
|
||||||
|
state->pending_request_used_tail = NULL;
|
||||||
|
state->pending_request_free_list = state->pending_request_pool;
|
||||||
|
for (size_t i = 0; i < ARP_MAX_PENDING_REQUESTS; i++)
|
||||||
|
state->pending_request_pool[i].next = state->pending_request_pool + i+1;
|
||||||
|
state->pending_request_pool[ARP_MAX_PENDING_REQUESTS-1].next = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void arp_free(arp_state_t *state)
|
||||||
|
{
|
||||||
|
arp_translation_table_free(&state->table);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void append_pending_request_to_used_list(arp_state_t *state, arp_pending_request_t *pending_request)
|
||||||
|
{
|
||||||
|
arp_pending_request_t *cursor = state->pending_request_used_list;
|
||||||
|
|
||||||
|
// Find the first pending request in the list
|
||||||
|
// with a lower timeout and insert the request
|
||||||
|
// before it.
|
||||||
|
while (cursor && cursor->timeout > pending_request->timeout)
|
||||||
|
cursor = cursor->next;
|
||||||
|
|
||||||
|
if (cursor) {
|
||||||
|
pending_request->prev = cursor->prev;
|
||||||
|
pending_request->next = cursor;
|
||||||
|
|
||||||
|
if (cursor->prev)
|
||||||
|
cursor->prev->next = pending_request;
|
||||||
|
else
|
||||||
|
state->pending_request_used_list = pending_request;
|
||||||
|
cursor->prev = pending_request;
|
||||||
|
} else {
|
||||||
|
// Insert the request in the tail of the list
|
||||||
|
pending_request->prev = state->pending_request_used_tail;
|
||||||
|
pending_request->next = NULL;
|
||||||
|
|
||||||
|
if (state->pending_request_used_tail)
|
||||||
|
state->pending_request_used_tail->next = pending_request;
|
||||||
|
else
|
||||||
|
state->pending_request_used_list = pending_request;
|
||||||
|
state->pending_request_used_tail = pending_request;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void arp_resolve_mac(arp_state_t *state, ip_address_t ip, void *userp, void (*callback)(void*, arp_resolution_status_t, mac_address_t))
|
||||||
|
{
|
||||||
|
bool found_mac_locally;
|
||||||
|
mac_address_t mac;
|
||||||
|
|
||||||
|
if (state->self_ip == ip) {
|
||||||
|
mac = state->self_mac;
|
||||||
|
found_mac_locally = true;
|
||||||
|
} else
|
||||||
|
found_mac_locally = arp_translation_table_find_mac_by_ip(&state->table, ip, &mac);
|
||||||
|
|
||||||
|
if (found_mac_locally)
|
||||||
|
callback(userp, ARP_RESOLUTION_OK, mac);
|
||||||
|
else {
|
||||||
|
|
||||||
|
// MAC isn't in the translation table.
|
||||||
|
// We need to make an ARP REQUEST
|
||||||
|
|
||||||
|
arp_pending_request_t *pending_request = state->pending_request_free_list;
|
||||||
|
if (pending_request == NULL) {
|
||||||
|
callback(userp, ARP_RESOLUTION_FAILED, MAC_ZERO);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state->pending_request_free_list = pending_request->next;
|
||||||
|
|
||||||
|
pending_request->ip = ip;
|
||||||
|
pending_request->timeout = state->time + state->request_timeout;
|
||||||
|
pending_request->callback = callback;
|
||||||
|
pending_request->callback_data = userp;
|
||||||
|
pending_request->prev = NULL;
|
||||||
|
pending_request->next = NULL;
|
||||||
|
|
||||||
|
append_pending_request_to_used_list(state, pending_request);
|
||||||
|
|
||||||
|
arp_packet_t *packet = state->output;
|
||||||
|
packet->hardware_type = htons(ARP_HARDWARE_ETHERNET);
|
||||||
|
packet->protocol_type = htons(ARP_PROTOCOL_IP);
|
||||||
|
packet->hardware_len = 6;
|
||||||
|
packet->protocol_len = 4;
|
||||||
|
packet->operation_type = htons(ARP_OPERATION_REQUEST);
|
||||||
|
packet->sender_hardware_address = state->self_mac;
|
||||||
|
packet->sender_protocol_address = state->self_ip;
|
||||||
|
packet->target_hardware_address = MAC_ZERO; // This is what we're trying to find
|
||||||
|
packet->target_protocol_address = ip;
|
||||||
|
|
||||||
|
ARP_DEBUG_LOG("Sending out ARP request to resolve MAC");
|
||||||
|
|
||||||
|
state->send(state->send_data, MAC_BROADCAST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
try_resolving_pending_requests(arp_state_t *state, ip_address_t ip, mac_address_t mac)
|
||||||
|
{
|
||||||
|
// NOTE: Could try resolving pending requests from
|
||||||
|
// the tail of the list instead of the head
|
||||||
|
// since the tail entries have been waiting
|
||||||
|
// longer. I think we can assume the older
|
||||||
|
// entries have higher chances of being resolved.
|
||||||
|
|
||||||
|
arp_pending_request_t *pending_request = state->pending_request_used_list;
|
||||||
|
arp_pending_request_t *prev = NULL;
|
||||||
|
while (pending_request) {
|
||||||
|
|
||||||
|
arp_pending_request_t *next = pending_request->next;
|
||||||
|
|
||||||
|
if (pending_request->ip == ip) {
|
||||||
|
pending_request->callback(pending_request->callback_data, ARP_RESOLUTION_OK, mac);
|
||||||
|
|
||||||
|
pending_request->next = state->pending_request_free_list;
|
||||||
|
state->pending_request_free_list = pending_request;
|
||||||
|
|
||||||
|
if (prev)
|
||||||
|
prev->next = next;
|
||||||
|
else
|
||||||
|
state->pending_request_used_list = next;
|
||||||
|
|
||||||
|
if (next)
|
||||||
|
next->prev = prev;
|
||||||
|
else
|
||||||
|
state->pending_request_used_tail = prev;
|
||||||
|
|
||||||
|
} else
|
||||||
|
prev = pending_request;
|
||||||
|
|
||||||
|
pending_request = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
arp_process_result_t arp_process_packet(arp_state_t *state, const void *packet, size_t len)
|
||||||
|
{
|
||||||
|
if (len != sizeof(arp_packet_t))
|
||||||
|
return ARP_PROCESS_RESULT_INVALID;
|
||||||
|
|
||||||
|
const arp_packet_t *packet2 = packet;
|
||||||
|
|
||||||
|
if (packet2->hardware_type != htons(ARP_HARDWARE_ETHERNET)) {
|
||||||
|
/* Level 2 protocol not supported */
|
||||||
|
ARP_DEBUG_LOG("Hardware type %d not supported", packet2->hardware_type);
|
||||||
|
return ARP_PROCESS_RESULT_HWARENOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet2->protocol_type != htons(ARP_PROTOCOL_IP)) {
|
||||||
|
/* Level 3 protocol not supported */
|
||||||
|
ARP_DEBUG_LOG("Protocol type %d not supported", packet2->protocol_type);
|
||||||
|
return ARP_PROCESS_RESULT_PROTONOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet2->hardware_len != 6 || packet2->protocol_len != 4) {
|
||||||
|
/* Invalid fields */
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool merge = arp_translation_table_update(&state->table, packet2->sender_hardware_address,
|
||||||
|
packet2->sender_protocol_address, state->cache_timeout);
|
||||||
|
|
||||||
|
if (packet2->target_protocol_address == state->self_ip) {
|
||||||
|
|
||||||
|
if (!merge) {
|
||||||
|
arp_translation_table_insert_or_update(&state->table, packet2->sender_hardware_address,
|
||||||
|
packet2->sender_protocol_address, state->cache_timeout);
|
||||||
|
try_resolving_pending_requests(state, packet2->sender_protocol_address,
|
||||||
|
packet2->sender_hardware_address);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet2->operation_type == htons(ARP_OPERATION_REQUEST)) {
|
||||||
|
|
||||||
|
// Generate the ARP REPLY
|
||||||
|
|
||||||
|
arp_packet_t *response = state->output;
|
||||||
|
response->hardware_type = packet2->hardware_type;
|
||||||
|
response->protocol_type = packet2->protocol_type;
|
||||||
|
response->hardware_len = packet2->hardware_len;
|
||||||
|
response->protocol_len = packet2->protocol_len;
|
||||||
|
response->operation_type = htons(ARP_OPERATION_REPLY);
|
||||||
|
response->sender_hardware_address = state->self_mac;
|
||||||
|
response->sender_protocol_address = state->self_ip;
|
||||||
|
response->target_hardware_address = packet2->sender_hardware_address;
|
||||||
|
response->target_protocol_address = packet2->sender_protocol_address;
|
||||||
|
|
||||||
|
ARP_DEBUG_LOG("Sending reply");
|
||||||
|
|
||||||
|
state->send(state->send_data, packet2->sender_hardware_address);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ARP_DEBUG_LOG("Request not for me");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ARP_PROCESS_RESULT_OK;
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include "defs.h"
|
||||||
|
|
||||||
|
#define ARP_MAX_PENDING_REQUESTS 32
|
||||||
|
#define ARP_TRANSLATION_TABLE_SIZE 128
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ARP_RESOLUTION_OK,
|
||||||
|
ARP_RESOLUTION_FAILED,
|
||||||
|
ARP_RESOLUTION_TIMEOUT,
|
||||||
|
} arp_resolution_status_t;
|
||||||
|
|
||||||
|
typedef struct arp_translation_table_entry_t arp_translation_table_entry_t;
|
||||||
|
struct arp_translation_table_entry_t {
|
||||||
|
arp_translation_table_entry_t *prev;
|
||||||
|
arp_translation_table_entry_t *next;
|
||||||
|
mac_address_t mac;
|
||||||
|
ip_address_t ip;
|
||||||
|
uint64_t timeout;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t time;
|
||||||
|
arp_translation_table_entry_t *used_list_head;
|
||||||
|
arp_translation_table_entry_t *used_list_tail;
|
||||||
|
arp_translation_table_entry_t *free_list;
|
||||||
|
arp_translation_table_entry_t entries[ARP_TRANSLATION_TABLE_SIZE];
|
||||||
|
} arp_translation_table_t;
|
||||||
|
|
||||||
|
typedef struct arp_pending_request_t arp_pending_request_t;
|
||||||
|
struct arp_pending_request_t {
|
||||||
|
arp_pending_request_t *prev;
|
||||||
|
arp_pending_request_t *next;
|
||||||
|
ip_address_t ip;
|
||||||
|
uint64_t timeout;
|
||||||
|
void *callback_data;
|
||||||
|
void (*callback)(void*, arp_resolution_status_t status, mac_address_t);
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct __attribute__((__packed__)) {
|
||||||
|
uint16_t hardware_type;
|
||||||
|
uint16_t protocol_type;
|
||||||
|
uint8_t hardware_len;
|
||||||
|
uint8_t protocol_len;
|
||||||
|
uint16_t operation_type;
|
||||||
|
mac_address_t sender_hardware_address;
|
||||||
|
ip_address_t sender_protocol_address;
|
||||||
|
mac_address_t target_hardware_address;
|
||||||
|
ip_address_t target_protocol_address;
|
||||||
|
} arp_packet_t;
|
||||||
|
|
||||||
|
static_assert(offsetof(arp_packet_t, hardware_type) == 0);
|
||||||
|
static_assert(offsetof(arp_packet_t, protocol_type) == 2);
|
||||||
|
static_assert(offsetof(arp_packet_t, hardware_len) == 4);
|
||||||
|
static_assert(offsetof(arp_packet_t, protocol_len) == 5);
|
||||||
|
static_assert(offsetof(arp_packet_t, operation_type) == 6);
|
||||||
|
static_assert(offsetof(arp_packet_t, sender_hardware_address) == 8);
|
||||||
|
static_assert(offsetof(arp_packet_t, sender_protocol_address) == 14);
|
||||||
|
static_assert(offsetof(arp_packet_t, target_hardware_address) == 18);
|
||||||
|
static_assert(offsetof(arp_packet_t, target_protocol_address) == 24);
|
||||||
|
static_assert(sizeof(arp_packet_t) == 28);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
uint64_t time;
|
||||||
|
uint64_t cache_timeout;
|
||||||
|
uint64_t request_timeout;
|
||||||
|
|
||||||
|
arp_packet_t *output;
|
||||||
|
|
||||||
|
void *send_data;
|
||||||
|
void (*send)(void *send_data, mac_address_t dest_mac);
|
||||||
|
|
||||||
|
ip_address_t self_ip;
|
||||||
|
mac_address_t self_mac;
|
||||||
|
arp_translation_table_t table;
|
||||||
|
|
||||||
|
arp_pending_request_t *pending_request_free_list;
|
||||||
|
arp_pending_request_t *pending_request_used_list;
|
||||||
|
arp_pending_request_t *pending_request_used_tail;
|
||||||
|
arp_pending_request_t pending_request_pool[ARP_MAX_PENDING_REQUESTS];
|
||||||
|
} arp_state_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ARP_PROCESS_RESULT_HWARENOTSUPP,
|
||||||
|
ARP_PROCESS_RESULT_PROTONOTSUPP,
|
||||||
|
ARP_PROCESS_RESULT_INVALID,
|
||||||
|
ARP_PROCESS_RESULT_OK,
|
||||||
|
} arp_process_result_t;
|
||||||
|
|
||||||
|
void arp_init(arp_state_t *state, ip_address_t ip, mac_address_t mac, void *send_data, void (*send)(void*, mac_address_t));
|
||||||
|
void arp_free(arp_state_t *state);
|
||||||
|
arp_process_result_t arp_process_packet(arp_state_t *state, const void *packet, size_t len);
|
||||||
|
void arp_resolve_mac(arp_state_t *state, ip_address_t ip, void *userp, void (*callback)(void*, arp_resolution_status_t, mac_address_t));
|
||||||
|
void arp_seconds_passed(arp_state_t *state, size_t seconds);
|
||||||
|
void arp_change_output_buffer(arp_state_t *state, void *ptr, size_t max);
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
#ifndef MICROTCP_DEFS_H
|
||||||
|
#define MICROTCP_DEFS_H
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <assert.h> // static_assert
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t data[6];
|
||||||
|
} mac_address_t;
|
||||||
|
|
||||||
|
typedef uint32_t ip_address_t;
|
||||||
|
|
||||||
|
static_assert(sizeof(mac_address_t) == 6);
|
||||||
|
static_assert(sizeof(ip_address_t) == 4);
|
||||||
|
|
||||||
|
#define MAC_ZERO (mac_address_t) {.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}
|
||||||
|
#define MAC_BROADCAST (mac_address_t) {.data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}
|
||||||
|
|
||||||
|
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
|
||||||
|
|
||||||
|
#endif /* MICROTCP_DEFS_H */
|
||||||
+113
@@ -0,0 +1,113 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include "icmp.h"
|
||||||
|
|
||||||
|
#ifdef ICMP_DEBUG
|
||||||
|
#include <stdio.h>
|
||||||
|
#define ICMP_DEBUG_LOG(fmt, ...) fprintf(stderr, "ICMP :: " fmt "\n", ## __VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define ICMP_DEBUG_LOG(...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ICMP_TYPE_ECHO_REPLY = 0,
|
||||||
|
ICMP_TYPE_ECHO_REQUEST = 8,
|
||||||
|
} icmp_type_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t type;
|
||||||
|
uint8_t code;
|
||||||
|
uint16_t checksum;
|
||||||
|
uint16_t id_no;
|
||||||
|
uint16_t seq_no;
|
||||||
|
uint8_t data[];
|
||||||
|
} icmp_message_echo_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t type;
|
||||||
|
uint8_t code;
|
||||||
|
uint16_t checksum;
|
||||||
|
uint8_t data[];
|
||||||
|
} icmp_message_generic_t;
|
||||||
|
|
||||||
|
void icmp_change_output_buffer(icmp_state_t *state, void *ptr, size_t len)
|
||||||
|
{
|
||||||
|
state->output_ptr = ptr;
|
||||||
|
state->output_len = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void icmp_init(icmp_state_t *state, void *send_data, void (*send)(void*, ip_address_t, size_t))
|
||||||
|
{
|
||||||
|
state->output_ptr = NULL;
|
||||||
|
state->output_len = 0;
|
||||||
|
state->send_data = send_data;
|
||||||
|
state->send = send;
|
||||||
|
}
|
||||||
|
|
||||||
|
void icmp_free(icmp_state_t *state)
|
||||||
|
{
|
||||||
|
(void) state;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t calculate_checksum_icmp(const void *src, size_t len)
|
||||||
|
{
|
||||||
|
assert((len & 1) == 0);
|
||||||
|
|
||||||
|
const uint16_t *src2 = src;
|
||||||
|
|
||||||
|
uint32_t sum = 0xffff;
|
||||||
|
for (size_t i = 0; i < len/2; i++) {
|
||||||
|
sum += ntohs(src2[i]);
|
||||||
|
if (sum > 0xffff)
|
||||||
|
sum -= 0xffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
return htons(~sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
void icmp_process_packet(icmp_state_t *state, ip_address_t ip, const void *src, size_t len)
|
||||||
|
{
|
||||||
|
if (len < sizeof(icmp_message_generic_t))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const icmp_message_generic_t *packet = src;
|
||||||
|
|
||||||
|
switch (packet->type) {
|
||||||
|
case ICMP_TYPE_ECHO_REQUEST:
|
||||||
|
{
|
||||||
|
if (len < sizeof(icmp_message_echo_t))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const icmp_message_echo_t *echo_request = (icmp_message_echo_t*) packet;
|
||||||
|
|
||||||
|
if (calculate_checksum_icmp(echo_request, len)) {
|
||||||
|
ICMP_DEBUG_LOG("Dropping ICMP message with invalid checksum");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
icmp_message_echo_t *echo_reply = state->output_ptr;
|
||||||
|
|
||||||
|
echo_reply->type = ICMP_TYPE_ECHO_REPLY;
|
||||||
|
echo_reply->code = 0;
|
||||||
|
echo_reply->checksum = 0;
|
||||||
|
echo_reply->id_no = echo_request->id_no;
|
||||||
|
echo_reply->seq_no = echo_request->seq_no;
|
||||||
|
memcpy(echo_reply->data, echo_request->data, len - sizeof(icmp_message_echo_t));
|
||||||
|
|
||||||
|
echo_reply->checksum = calculate_checksum_icmp(echo_reply, len);
|
||||||
|
|
||||||
|
ICMP_DEBUG_LOG("Replying to echo request");
|
||||||
|
state->send(state->send_data, ip, len);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Unsupported ICMP message
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
#include <stddef.h>
|
||||||
|
#include "defs.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
void *output_ptr;
|
||||||
|
size_t output_len;
|
||||||
|
void *send_data;
|
||||||
|
void (*send)(void *send_data, ip_address_t ip, size_t len);
|
||||||
|
} icmp_state_t;
|
||||||
|
|
||||||
|
void icmp_init(icmp_state_t *state, void *send_data, void (*send)(void*, ip_address_t, size_t));
|
||||||
|
void icmp_free(icmp_state_t *state);
|
||||||
|
void icmp_process_packet(icmp_state_t *state, ip_address_t ip, const void *src, size_t len);
|
||||||
|
void icmp_change_output_buffer(icmp_state_t *state, void *ptr, size_t len);
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include <arpa/inet.h> // ntohs()
|
||||||
|
#include "ip.h"
|
||||||
|
|
||||||
|
#ifdef IP_DEBUG
|
||||||
|
#include <stdio.h>
|
||||||
|
#define IP_DEBUG_LOG(fmt, ...) do { fprintf(stderr, "IP :: " fmt "\n", ## __VA_ARGS__); } while (0);
|
||||||
|
#else
|
||||||
|
#define IP_DEBUG_LOG(...) do { } while (0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
static uint16_t calculate_checksum_ip(const void *src, size_t len)
|
||||||
|
{
|
||||||
|
assert((len & 1) == 0);
|
||||||
|
|
||||||
|
const uint16_t *src2 = src;
|
||||||
|
|
||||||
|
uint32_t sum = 0xffff;
|
||||||
|
for (size_t i = 0; i < len/2; i++) {
|
||||||
|
sum += ntohs(src2[i]);
|
||||||
|
if (sum > 0xffff)
|
||||||
|
sum -= 0xffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
return htons(~sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static ip_plugged_protocol_t *
|
||||||
|
find_protocol_with_id(ip_state_t *ip_state, uint8_t protocol)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < ip_state->plugged_protocols_count; i++)
|
||||||
|
if (protocol == ip_state->plugged_protocols[i].protocol)
|
||||||
|
return ip_state->plugged_protocols + i;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ip_plug_protocol(ip_state_t *ip_state, uint8_t protocol,
|
||||||
|
void *data, void (*process_packet)(void *data, ip_address_t sender, const void *packet, size_t len))
|
||||||
|
{
|
||||||
|
if (protocol == IP_PROTOCOL_ICMP)
|
||||||
|
return false; // Can't override default ICMP module
|
||||||
|
|
||||||
|
ip_plugged_protocol_t *p = find_protocol_with_id(ip_state, protocol);
|
||||||
|
|
||||||
|
if (!p) {
|
||||||
|
if (ip_state->plugged_protocols_count == IP_PLUGGED_PROTOCOLS_MAX)
|
||||||
|
return false;
|
||||||
|
p = ip_state->plugged_protocols + ip_state->plugged_protocols_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
p->protocol = protocol;
|
||||||
|
p->data = data;
|
||||||
|
p->process_packet = process_packet;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_packet_one_of_more_fragments(const ip_packet_t *packet)
|
||||||
|
{
|
||||||
|
size_t offset = ntohs(packet->fragment_offset) & 0x1FFF;
|
||||||
|
bool more_fragments = ntohs(packet->fragment_offset) & 0x2000;
|
||||||
|
return more_fragments || offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void send_icmp_packet(void *data, ip_address_t ip, size_t len)
|
||||||
|
{
|
||||||
|
ip_state_t *ip_state = data;
|
||||||
|
|
||||||
|
// The data was written in the output buffer
|
||||||
|
ip_packet_t *packet = ip_state->output_ptr; // This changes every iteration
|
||||||
|
packet->version = 4;
|
||||||
|
packet->header_length = 5;
|
||||||
|
packet->type_of_service = 0; // ???
|
||||||
|
packet->total_length = htons(sizeof(ip_packet_t) + len);
|
||||||
|
packet->id = ip_state->next_id++;
|
||||||
|
packet->fragment_offset = 0; // ???
|
||||||
|
packet->time_to_live = 32; // ???
|
||||||
|
packet->protocol = IP_PROTOCOL_ICMP;
|
||||||
|
packet->checksum = 0; // Temporary value
|
||||||
|
packet->src_ip = ip_state->ip;
|
||||||
|
packet->dst_ip = ip;
|
||||||
|
|
||||||
|
packet->checksum = calculate_checksum_ip((uint16_t*) packet, 4 * packet->header_length);
|
||||||
|
|
||||||
|
ip_state->send(ip_state->send_data, ip, sizeof(ip_packet_t) + len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ip_init(ip_state_t *state,
|
||||||
|
ip_address_t ip,
|
||||||
|
void *send_data,
|
||||||
|
void (*send)(void*, ip_address_t, size_t))
|
||||||
|
{
|
||||||
|
state->ip = ip;
|
||||||
|
state->next_id = 0;
|
||||||
|
state->send_data = send_data;
|
||||||
|
state->send = send;
|
||||||
|
state->output_ptr = NULL;
|
||||||
|
state->output_max = 0;
|
||||||
|
state->plugged_protocols_count = 0;
|
||||||
|
icmp_init(&state->icmp_state, state, send_icmp_packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ip_free(ip_state_t *ip_state)
|
||||||
|
{
|
||||||
|
icmp_free(&ip_state->icmp_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ip_change_output_buffer(ip_state_t *state, void *ptr, size_t max)
|
||||||
|
{
|
||||||
|
state->output_ptr = ptr;
|
||||||
|
state->output_max = max;
|
||||||
|
icmp_change_output_buffer(&state->icmp_state, (ip_packet_t*) ptr + 1, max - sizeof(ip_packet_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ip_seconds_passed(ip_state_t *state, size_t seconds)
|
||||||
|
{
|
||||||
|
(void) state;
|
||||||
|
(void) seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ip_send(ip_state_t *state, ip_protocol_t protocol, ip_address_t dst, bool no_fragm, const void *src, size_t len)
|
||||||
|
{
|
||||||
|
size_t managed_payload = 0;
|
||||||
|
|
||||||
|
while (managed_payload < len && (managed_payload == 0 || !no_fragm)) {
|
||||||
|
|
||||||
|
if (state->output_ptr == NULL) {
|
||||||
|
// Lower layers of the network stack didn't specify an output
|
||||||
|
// buffer region. This may be because no memory is available.
|
||||||
|
|
||||||
|
// If at least one byte was sent, return gracefully.
|
||||||
|
// If no byte was sent return an error to the caller.
|
||||||
|
if (managed_payload > 0)
|
||||||
|
break;
|
||||||
|
else
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state->output_max <= sizeof(ip_packet_t))
|
||||||
|
// Output buffer provided by the lower layers of the stack
|
||||||
|
// isn't big enough for an IP packet containing a single byte.
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
size_t current_payload_limit = state->output_max - sizeof(ip_packet_t);
|
||||||
|
size_t remaining_payload = len - managed_payload;
|
||||||
|
size_t considered_payload = MIN(current_payload_limit, remaining_payload);
|
||||||
|
|
||||||
|
ip_packet_t *packet = state->output_ptr; // This changes every iteration
|
||||||
|
packet->version = 4;
|
||||||
|
packet->header_length = 5;
|
||||||
|
packet->type_of_service = 0; // ???
|
||||||
|
packet->total_length = htons(sizeof(ip_packet_t) + considered_payload);
|
||||||
|
packet->id = state->next_id++;
|
||||||
|
packet->fragment_offset = 0; // ???
|
||||||
|
packet->time_to_live = 32; // ???
|
||||||
|
packet->protocol = protocol;
|
||||||
|
packet->checksum = 0; // Temporary value
|
||||||
|
packet->src_ip = state->ip;
|
||||||
|
packet->dst_ip = dst;
|
||||||
|
memcpy(packet->payload,
|
||||||
|
src + managed_payload,
|
||||||
|
considered_payload);
|
||||||
|
|
||||||
|
packet->checksum = calculate_checksum_ip((uint16_t*) packet, 4 * packet->header_length);
|
||||||
|
|
||||||
|
// Sending updates the [state->output_ptr] and [state->output_len]
|
||||||
|
state->send(state->send_data, dst, sizeof(ip_packet_t) + considered_payload);
|
||||||
|
|
||||||
|
managed_payload += considered_payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
return managed_payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ip_process_packet(ip_state_t *ip_state, const void *packet, size_t len)
|
||||||
|
{
|
||||||
|
if (len < sizeof(ip_packet_t))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const ip_packet_t *packet2 = packet;
|
||||||
|
|
||||||
|
if (packet2->version != 4 || packet2->header_length < 5) {
|
||||||
|
IP_DEBUG_LOG("Only supported IPv4 (received %d) with no options", packet2->version);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t option_count = packet2->header_length - sizeof(ip_packet_t)/4;
|
||||||
|
if (option_count > 0) {
|
||||||
|
#warning "TODO: Handle IP options"
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_packet_one_of_more_fragments(packet2)) {
|
||||||
|
IP_DEBUG_LOG("Not supporting IP fragmentation");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (calculate_checksum_ip((uint16_t*) packet2, 4 * packet2->header_length)) {
|
||||||
|
IP_DEBUG_LOG("Dropping IP packet with invalid checksum");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet2->dst_ip != ip_state->ip) {
|
||||||
|
IP_DEBUG_LOG("Packet not for me");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ip_plugged_protocol_t *handler = find_protocol_with_id(ip_state, packet2->protocol);
|
||||||
|
|
||||||
|
const void *packet3_ptr = packet2+1;
|
||||||
|
size_t packet3_len = ntohs(packet2->total_length) - sizeof(ip_packet_t);
|
||||||
|
|
||||||
|
if (handler)
|
||||||
|
handler->process_packet(handler->data, packet2->src_ip, packet3_ptr, packet3_len);
|
||||||
|
else if (packet2->protocol == IP_PROTOCOL_ICMP)
|
||||||
|
icmp_process_packet(&ip_state->icmp_state, packet2->src_ip, packet3_ptr, packet3_len);
|
||||||
|
else
|
||||||
|
IP_DEBUG_LOG("Unsupported protocol %d", packet2->protocol);
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <endian.h>
|
||||||
|
#include "defs.h"
|
||||||
|
#include "icmp.h"
|
||||||
|
|
||||||
|
#define IP_PLUGGED_PROTOCOLS_MAX 4
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||||
|
uint8_t header_length: 4;
|
||||||
|
uint8_t version: 4;
|
||||||
|
#endif
|
||||||
|
#if __BYTE_ORDER == __BIG_ENDIAN
|
||||||
|
uint8_t version: 4;
|
||||||
|
uint8_t header_length: 4;
|
||||||
|
#endif
|
||||||
|
uint8_t type_of_service;
|
||||||
|
uint16_t total_length;
|
||||||
|
uint16_t id;
|
||||||
|
uint16_t fragment_offset;
|
||||||
|
uint8_t time_to_live;
|
||||||
|
uint8_t protocol;
|
||||||
|
uint16_t checksum;
|
||||||
|
uint32_t src_ip;
|
||||||
|
uint32_t dst_ip;
|
||||||
|
char payload[];
|
||||||
|
} ip_packet_t;
|
||||||
|
static_assert(sizeof(ip_packet_t) == 20);
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
IP_PROTOCOL_ICMP = 1,
|
||||||
|
IP_PROTOCOL_TCP = 6,
|
||||||
|
IP_PROTOCOL_UDP = 17,
|
||||||
|
} ip_protocol_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t protocol;
|
||||||
|
void *data;
|
||||||
|
void (*process_packet)(void*, ip_address_t, const void*, size_t);
|
||||||
|
} ip_plugged_protocol_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ip_address_t ip;
|
||||||
|
|
||||||
|
uint32_t next_id;
|
||||||
|
|
||||||
|
void *output_ptr;
|
||||||
|
size_t output_max;
|
||||||
|
|
||||||
|
icmp_state_t icmp_state;
|
||||||
|
|
||||||
|
void *send_data;
|
||||||
|
void (*send)(void*, ip_address_t, size_t);
|
||||||
|
|
||||||
|
size_t plugged_protocols_count;
|
||||||
|
ip_plugged_protocol_t plugged_protocols[IP_PLUGGED_PROTOCOLS_MAX];
|
||||||
|
} ip_state_t;
|
||||||
|
|
||||||
|
void ip_seconds_passed(ip_state_t *state, size_t seconds);
|
||||||
|
void ip_change_output_buffer(ip_state_t *state, void *ptr, size_t max);
|
||||||
|
void ip_init(ip_state_t *state, ip_address_t ip, void *send_data, void (*send)(void*, ip_address_t, size_t));
|
||||||
|
void ip_free(ip_state_t *state);
|
||||||
|
int ip_send(ip_state_t *state, ip_protocol_t protocol, ip_address_t dst, bool no_fragm, const void *src, size_t len);
|
||||||
|
void ip_process_packet(ip_state_t *state, const void *packet, size_t len);
|
||||||
|
bool ip_plug_protocol(ip_state_t *ip_state, uint8_t protocol, void *data, void (*process_packet)(void *data, ip_address_t sender, const void *packet, size_t len));
|
||||||
+758
@@ -0,0 +1,758 @@
|
|||||||
|
#include <time.h> // time()
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h> // strerror()
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
|
||||||
|
#include "ip.h"
|
||||||
|
#include "arp.h"
|
||||||
|
#include "tcp.h"
|
||||||
|
#include <microtcp.h>
|
||||||
|
|
||||||
|
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||||
|
#include <pthread.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef MICROTCP_DEBUG
|
||||||
|
#include <stdio.h>
|
||||||
|
#define MICROTCP_DEBUG_LOG(fmt, ...) do { fprintf(stderr, "MICROTCP :: " fmt "\n", ## __VA_ARGS__); } while (0);
|
||||||
|
#else
|
||||||
|
#define MICROTCP_DEBUG_LOG(...) do {} while (0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||||
|
#define LOCK_WHEN_THREADED(mtcp) do { \
|
||||||
|
fprintf(stderr, "--- %s before lock\n", __func__); \
|
||||||
|
fflush(stderr); \
|
||||||
|
pthread_mutex_lock(&(mtcp)->lock); \
|
||||||
|
fprintf(stderr, "--- %s after lock\n", __func__); \
|
||||||
|
fflush(stderr); \
|
||||||
|
} while (0);
|
||||||
|
|
||||||
|
#define UNLOCK_WHEN_THREADED(mtcp) do { \
|
||||||
|
fprintf(stderr, "--- %s before unlock\n", __func__); \
|
||||||
|
fflush(stderr); \
|
||||||
|
pthread_mutex_unlock(&(mtcp)->lock); \
|
||||||
|
fprintf(stderr, "--- %s after unlock\n", __func__); \
|
||||||
|
fflush(stderr); \
|
||||||
|
} while (0);
|
||||||
|
|
||||||
|
//#define UNLOCK_WHEN_THREADED(mtcp) do { pthread_mutex_unlock(&(mtcp)->lock); } while (0);
|
||||||
|
#else
|
||||||
|
#define LOCK_WHEN_THREADED(mtcp) do { (void) (mtcp); } while (0);
|
||||||
|
#define UNLOCK_WHEN_THREADED(mtcp) do { (void) (mtcp); } while (0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct buffer_t buffer_t;
|
||||||
|
struct buffer_t {
|
||||||
|
microtcp_t *mtcp;
|
||||||
|
buffer_t *prev;
|
||||||
|
buffer_t *next;
|
||||||
|
size_t used;
|
||||||
|
char data[1518];
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SOCKET_LISTENER,
|
||||||
|
SOCKET_CONNECTION,
|
||||||
|
} socket_type_t;
|
||||||
|
|
||||||
|
struct microtcp_socket_t {
|
||||||
|
microtcp_t *mtcp;
|
||||||
|
microtcp_socket_t *prev;
|
||||||
|
microtcp_socket_t *next;
|
||||||
|
socket_type_t type;
|
||||||
|
union {
|
||||||
|
tcp_listener_t *listener;
|
||||||
|
tcp_connection_t *connection;
|
||||||
|
};
|
||||||
|
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||||
|
pthread_cond_t something_to_accept;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
struct microtcp_t {
|
||||||
|
|
||||||
|
time_t last_update_time;
|
||||||
|
|
||||||
|
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||||
|
bool thread_should_stop;
|
||||||
|
pthread_t thread_id;
|
||||||
|
pthread_mutex_t lock;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
microtcp_callbacks_t callbacks;
|
||||||
|
|
||||||
|
ip_address_t ip;
|
||||||
|
mac_address_t mac;
|
||||||
|
|
||||||
|
ip_state_t ip_state;
|
||||||
|
arp_state_t arp_state;
|
||||||
|
tcp_state_t tcp_state;
|
||||||
|
|
||||||
|
buffer_t *used_buffer;
|
||||||
|
buffer_t *wait_buffer_list;
|
||||||
|
buffer_t *free_buffer_list;
|
||||||
|
buffer_t buffer_pool[MICROTCP_MAX_BUFFERS];
|
||||||
|
|
||||||
|
microtcp_socket_t *used_socket_list;
|
||||||
|
microtcp_socket_t *free_socket_list;
|
||||||
|
microtcp_socket_t socket_pool[MICROTCP_MAX_SOCKETS];
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *microtcp_strerror(microtcp_errcode_t errcode)
|
||||||
|
{
|
||||||
|
switch (errcode) {
|
||||||
|
case MICROTCP_ERRCODE_NONE: return "No error occurred";
|
||||||
|
case MICROTCP_ERRCODE_SOCKETLIMIT: return "Can't create a socket because the socket limit per microtcp instance was reached";
|
||||||
|
case MICROTCP_ERRCODE_TCPERROR: return "An error occurred at the TCP layer";
|
||||||
|
case MICROTCP_ERRCODE_BADCONDVAR: return "Condition variable error";
|
||||||
|
case MICROTCP_ERRCODE_NOTLISTENER: return "Invalid operation on a non-listener socket";
|
||||||
|
case MICROTCP_ERRCODE_CANTBLOCK: return "Can't execute a blocking call for this function";
|
||||||
|
case MICROTCP_ERRCODE_NOTHINGTOACCEPT: return "Accept queue is empty";
|
||||||
|
case MICROTCP_ERRCODE_NOTCONNECTION: return "Invalid operation on a non-connection socket";
|
||||||
|
}
|
||||||
|
return "???";
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ETHERNET_PROTOCOL_ARP = 0x0806,
|
||||||
|
ETHERNET_PROTOCOL_IP = 0x0800,
|
||||||
|
} ethernet_protocol_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
mac_address_t dst;
|
||||||
|
mac_address_t src;
|
||||||
|
uint16_t proto;
|
||||||
|
} __attribute__((packed)) ethernet_frame_t;
|
||||||
|
|
||||||
|
static_assert(sizeof(ethernet_frame_t) == 14);
|
||||||
|
|
||||||
|
#ifdef MICROTCP_DEBUG
|
||||||
|
static bool is_valid_buffer_pointer(microtcp_t *mtcp, buffer_t *buffer)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < MICROTCP_MAX_BUFFERS; i++)
|
||||||
|
if (buffer == mtcp->buffer_pool + i)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void send_arp_packet(void *data, mac_address_t dst)
|
||||||
|
{
|
||||||
|
microtcp_t *mtcp = data;
|
||||||
|
buffer_t *buffer = mtcp->used_buffer;
|
||||||
|
|
||||||
|
#ifdef MICROTCP_DEBUG
|
||||||
|
assert(is_valid_buffer_pointer(mtcp, buffer));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
buffer->used = sizeof(ethernet_frame_t) + sizeof(arp_packet_t);
|
||||||
|
|
||||||
|
ethernet_frame_t *frame = (ethernet_frame_t*) buffer->data;
|
||||||
|
frame->dst = dst;
|
||||||
|
frame->src = mtcp->mac;
|
||||||
|
frame->proto = htons(ETHERNET_PROTOCOL_ARP);
|
||||||
|
|
||||||
|
// TODO: What about the CRC?
|
||||||
|
#warning "TODO: Calculate Ethernet CRC"
|
||||||
|
|
||||||
|
int n = mtcp->callbacks.send(mtcp->callbacks.data, buffer->data, buffer->used);
|
||||||
|
if (n < 0)
|
||||||
|
MICROTCP_DEBUG_LOG("Couldn't send (%s)", strerror(errno));
|
||||||
|
|
||||||
|
// Now reset the used buffer
|
||||||
|
mtcp->used_buffer->used = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int send_tcp_segment(void *data, ip_address_t dst,
|
||||||
|
const void *str, size_t len)
|
||||||
|
{
|
||||||
|
microtcp_t *mtcp = data;
|
||||||
|
return ip_send(&mtcp->ip_state, IP_PROTOCOL_TCP, dst, true, str, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void move_wait_buffer_to_free_list(buffer_t *buffer)
|
||||||
|
{
|
||||||
|
microtcp_t *mtcp = buffer->mtcp;
|
||||||
|
|
||||||
|
#ifdef MICROTCP_DEBUG
|
||||||
|
assert(is_valid_buffer_pointer(mtcp, buffer));
|
||||||
|
assert(buffer->prev == NULL || is_valid_buffer_pointer(mtcp, buffer->prev));
|
||||||
|
assert(buffer->next == NULL || is_valid_buffer_pointer(mtcp, buffer->next));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (buffer->prev)
|
||||||
|
buffer->prev->next = buffer->next;
|
||||||
|
else
|
||||||
|
mtcp->wait_buffer_list = buffer->next;
|
||||||
|
|
||||||
|
if (buffer->next)
|
||||||
|
buffer->next->prev = buffer->prev;
|
||||||
|
|
||||||
|
#ifdef MICROTCP_DEBUG
|
||||||
|
assert(mtcp->free_buffer_list == NULL || is_valid_buffer_pointer(mtcp, mtcp->free_buffer_list));
|
||||||
|
assert(mtcp->free_buffer_list == NULL || mtcp->free_buffer_list->prev == NULL);
|
||||||
|
assert(mtcp->free_buffer_list == NULL || mtcp->free_buffer_list->next == NULL || is_valid_buffer_pointer(mtcp, mtcp->free_buffer_list->next));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
buffer->prev = NULL;
|
||||||
|
buffer->next = mtcp->free_buffer_list;
|
||||||
|
mtcp->free_buffer_list = buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mac_resolved(void *data, arp_resolution_status_t status, mac_address_t mac)
|
||||||
|
{
|
||||||
|
buffer_t *buffer = data;
|
||||||
|
microtcp_t *mtcp = buffer->mtcp;
|
||||||
|
|
||||||
|
#ifdef MICROTCP_DEBUG
|
||||||
|
assert(is_valid_buffer_pointer(mtcp, buffer));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case ARP_RESOLUTION_OK:
|
||||||
|
{
|
||||||
|
ethernet_frame_t *frame = (ethernet_frame_t*) buffer->data;
|
||||||
|
frame->dst = mac;
|
||||||
|
|
||||||
|
int n = mtcp->callbacks.send(mtcp->callbacks.data, buffer->data, buffer->used);
|
||||||
|
if (n < 0)
|
||||||
|
MICROTCP_DEBUG_LOG("Couldn't send (%s)", strerror(errno));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARP_RESOLUTION_FAILED:
|
||||||
|
MICROTCP_DEBUG_LOG("MAC resolution failed");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARP_RESOLUTION_TIMEOUT:
|
||||||
|
MICROTCP_DEBUG_LOG("MAC resolution timeout");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
move_wait_buffer_to_free_list(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void move_used_buffer_to_wait_list(microtcp_t *mtcp)
|
||||||
|
{
|
||||||
|
buffer_t *buffer = mtcp->used_buffer;
|
||||||
|
mtcp->used_buffer = NULL;
|
||||||
|
|
||||||
|
#ifdef MICROTCP_DEBUG
|
||||||
|
assert(is_valid_buffer_pointer(mtcp, buffer));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
buffer->next = mtcp->wait_buffer_list;
|
||||||
|
if (mtcp->wait_buffer_list)
|
||||||
|
mtcp->wait_buffer_list->prev = buffer;
|
||||||
|
mtcp->wait_buffer_list = buffer;
|
||||||
|
|
||||||
|
ip_change_output_buffer(&mtcp->ip_state, NULL, 0);
|
||||||
|
arp_change_output_buffer(&mtcp->arp_state, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void use_a_buffer(microtcp_t *mtcp)
|
||||||
|
{
|
||||||
|
|
||||||
|
#ifdef MIRCOTCP_DEBUG
|
||||||
|
assert(mtcp->free_buffer_list == NULL || is_valid_buffer_pointer(mtcp, mtcp->free_buffer_list));
|
||||||
|
assert(mtcp->free_buffer_list == NULL || mtcp->free_buffer_list->prev == NULL);
|
||||||
|
assert(mtcp->free_buffer_list == NULL || mtcp->free_buffer_list->next == NULL || is_valid_buffer_pointer(mtcp, mtcp->free_buffer_list->next));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// At this moment the network stack has no allocated
|
||||||
|
// output buffer but wants to allocate one (by calling
|
||||||
|
// this function).
|
||||||
|
// It's assumed there is no output buffer, hence:
|
||||||
|
//
|
||||||
|
assert(mtcp->used_buffer == NULL);
|
||||||
|
//
|
||||||
|
// To allocate a buffer, we need to pop it from the
|
||||||
|
// buffer free list, which is a singly-linked list of
|
||||||
|
// unused buffers. Once it's been popped off the list,
|
||||||
|
// we need to tell the upper layers of the stack that
|
||||||
|
// this is the new output buffer.
|
||||||
|
//
|
||||||
|
// If the free list is empty, no buffer is allocated.
|
||||||
|
//
|
||||||
|
if (!mtcp->free_buffer_list)
|
||||||
|
return; // No free buffers available in the free list.
|
||||||
|
//
|
||||||
|
// Pop a buffer from the free list
|
||||||
|
buffer_t *buffer = mtcp->free_buffer_list;
|
||||||
|
mtcp->free_buffer_list = buffer->next;
|
||||||
|
//
|
||||||
|
// Initialize the buffer
|
||||||
|
buffer->mtcp = mtcp;
|
||||||
|
buffer->used = 0;
|
||||||
|
buffer->prev = NULL;
|
||||||
|
buffer->next = NULL;
|
||||||
|
//
|
||||||
|
// Set it as the output buffer
|
||||||
|
mtcp->used_buffer = buffer;
|
||||||
|
//
|
||||||
|
// Now tell the upper layers where they'll output
|
||||||
|
// the data, but reserve the first bytes of the buffer
|
||||||
|
// for the ethernet header.
|
||||||
|
//
|
||||||
|
void *output_ptr = buffer->data + sizeof(ethernet_frame_t);
|
||||||
|
size_t output_max = sizeof(buffer->data) - sizeof(ethernet_frame_t);
|
||||||
|
ip_change_output_buffer(&mtcp->ip_state, output_ptr, output_max);
|
||||||
|
arp_change_output_buffer(&mtcp->arp_state, output_ptr, output_max);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void send_ip_packet(void *data, ip_address_t ip, size_t len)
|
||||||
|
{
|
||||||
|
microtcp_t *mtcp = data;
|
||||||
|
|
||||||
|
buffer_t *buffer = mtcp->used_buffer;
|
||||||
|
if (buffer == NULL)
|
||||||
|
// The IP layer wants to send something, but no output
|
||||||
|
// buffer was associated to it. This function should not
|
||||||
|
// have been called by the IP layer without a buffer.
|
||||||
|
return;
|
||||||
|
|
||||||
|
buffer->used = sizeof(ethernet_frame_t) + len;
|
||||||
|
|
||||||
|
move_used_buffer_to_wait_list(mtcp);
|
||||||
|
use_a_buffer(mtcp);
|
||||||
|
|
||||||
|
ethernet_frame_t *frame = (ethernet_frame_t*) buffer->data;
|
||||||
|
frame->src = mtcp->mac;
|
||||||
|
frame->dst = MAC_ZERO; // We need to determine it
|
||||||
|
frame->proto = htons(ETHERNET_PROTOCOL_IP);
|
||||||
|
|
||||||
|
arp_resolve_mac(&mtcp->arp_state, ip, buffer, mac_resolved);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
tcp_process_segment_wrapper(void *data, ip_address_t ip, const void *packet, size_t len)
|
||||||
|
{
|
||||||
|
if (len >= sizeof(tcp_segment_t))
|
||||||
|
tcp_process_segment((tcp_state_t*) data, ip, (tcp_segment_t*) packet, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
process_packet(microtcp_t *mtcp, const void *packet, size_t len)
|
||||||
|
{
|
||||||
|
if (len < sizeof(ethernet_frame_t))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const ethernet_frame_t *frame = packet;
|
||||||
|
|
||||||
|
switch (ntohs(frame->proto)) {
|
||||||
|
case ETHERNET_PROTOCOL_ARP:
|
||||||
|
arp_process_packet(&mtcp->arp_state, frame+1, len - sizeof(ethernet_frame_t));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ETHERNET_PROTOCOL_IP:
|
||||||
|
ip_process_packet(&mtcp->ip_state, frame+1, len - sizeof(ethernet_frame_t));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Unsupported ethertype
|
||||||
|
MICROTCP_DEBUG_LOG("Ignoring packet with ethertype %4x", frame->proto);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void microtcp_process_packet(microtcp_t *mtcp, const void *packet, size_t len)
|
||||||
|
{
|
||||||
|
LOCK_WHEN_THREADED(mtcp);
|
||||||
|
process_packet(mtcp, packet, len);
|
||||||
|
UNLOCK_WHEN_THREADED(mtcp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void microtcp_step(microtcp_t *mtcp)
|
||||||
|
{
|
||||||
|
char packet[UINT16_MAX];
|
||||||
|
|
||||||
|
// The call to [recv] (which is assumed to be blocking)
|
||||||
|
// needs to be out of the critical section to give other
|
||||||
|
// threads the ability to progress in the mean time.
|
||||||
|
int size = mtcp->callbacks.recv(mtcp->callbacks.data, packet, sizeof(packet));
|
||||||
|
if (size < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
LOCK_WHEN_THREADED(mtcp);
|
||||||
|
{
|
||||||
|
process_packet(mtcp, packet, size);
|
||||||
|
|
||||||
|
time_t current_time = time(NULL);
|
||||||
|
int secs = (float) (current_time - mtcp->last_update_time);
|
||||||
|
|
||||||
|
if (secs > 0) {
|
||||||
|
ip_seconds_passed(&mtcp->ip_state, secs);
|
||||||
|
arp_seconds_passed(&mtcp->arp_state, secs);
|
||||||
|
tcp_seconds_passed(&mtcp->tcp_state, secs);
|
||||||
|
mtcp->last_update_time = current_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unlock_and_exit:
|
||||||
|
UNLOCK_WHEN_THREADED(mtcp);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||||
|
static void *loop(void *data)
|
||||||
|
{
|
||||||
|
microtcp_t *mtcp = data;
|
||||||
|
while (!mtcp->thread_should_stop)
|
||||||
|
microtcp_step(mtcp);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
microtcp_t *microtcp_create_using_callbacks(microtcp_ip_t ip, microtcp_mac_t mac,
|
||||||
|
microtcp_callbacks_t callbacks)
|
||||||
|
{
|
||||||
|
microtcp_t *mtcp = malloc(sizeof(microtcp_t));
|
||||||
|
if (mtcp == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
mac_address_t mac2;
|
||||||
|
static_assert(sizeof(mac2) == sizeof(mac));
|
||||||
|
memcpy(&mac2, &mac, sizeof(mac));
|
||||||
|
|
||||||
|
mtcp->ip = ip;
|
||||||
|
mtcp->mac = mac2;
|
||||||
|
mtcp->callbacks = callbacks;
|
||||||
|
mtcp->last_update_time = time(NULL);
|
||||||
|
|
||||||
|
mtcp->used_buffer = NULL;
|
||||||
|
mtcp->wait_buffer_list = NULL;
|
||||||
|
mtcp->free_buffer_list = mtcp->buffer_pool;
|
||||||
|
for (size_t i = 0; i < MICROTCP_MAX_BUFFERS-1; i++) {
|
||||||
|
mtcp->buffer_pool[i].mtcp = NULL;
|
||||||
|
mtcp->buffer_pool[i].prev = NULL;
|
||||||
|
mtcp->buffer_pool[i].next = mtcp->buffer_pool + i+1;
|
||||||
|
}
|
||||||
|
mtcp->buffer_pool[MICROTCP_MAX_BUFFERS-1].mtcp = NULL;
|
||||||
|
mtcp->buffer_pool[MICROTCP_MAX_BUFFERS-1].prev = NULL;
|
||||||
|
mtcp->buffer_pool[MICROTCP_MAX_BUFFERS-1].next = NULL;
|
||||||
|
|
||||||
|
mtcp->used_socket_list = NULL;
|
||||||
|
mtcp->free_socket_list = mtcp->socket_pool;
|
||||||
|
for (size_t i = 0; i < MICROTCP_MAX_SOCKETS-1; i++) {
|
||||||
|
mtcp->socket_pool[i].mtcp = NULL;
|
||||||
|
mtcp->socket_pool[i].prev = NULL;
|
||||||
|
mtcp->socket_pool[i].next = mtcp->socket_pool + i + 1;
|
||||||
|
}
|
||||||
|
mtcp->socket_pool[MICROTCP_MAX_SOCKETS-1].mtcp = NULL;
|
||||||
|
mtcp->socket_pool[MICROTCP_MAX_SOCKETS-1].prev = NULL;
|
||||||
|
mtcp->socket_pool[MICROTCP_MAX_SOCKETS-1].next = NULL;
|
||||||
|
|
||||||
|
ip_init(&mtcp->ip_state, ip, mtcp, send_ip_packet);
|
||||||
|
if (!ip_plug_protocol(&mtcp->ip_state, IP_PROTOCOL_TCP, &mtcp->tcp_state, tcp_process_segment_wrapper)) {
|
||||||
|
free(mtcp);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
arp_init(&mtcp->arp_state, ip, mac2, mtcp, send_arp_packet);
|
||||||
|
|
||||||
|
tcp_init(&mtcp->tcp_state, (tcp_callbacks_t) {
|
||||||
|
.data = mtcp,
|
||||||
|
.send = send_tcp_segment,
|
||||||
|
});
|
||||||
|
|
||||||
|
use_a_buffer(mtcp);
|
||||||
|
|
||||||
|
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||||
|
{
|
||||||
|
if (pthread_mutex_init(&mtcp->lock, NULL)) {
|
||||||
|
ip_free(&mtcp->ip_state);
|
||||||
|
arp_free(&mtcp->arp_state);
|
||||||
|
tcp_free(&mtcp->tcp_state);
|
||||||
|
free(mtcp);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
mtcp->thread_should_stop = false;
|
||||||
|
if (pthread_create(&mtcp->thread_id, NULL, loop, mtcp)) {
|
||||||
|
ip_free(&mtcp->ip_state);
|
||||||
|
arp_free(&mtcp->arp_state);
|
||||||
|
tcp_free(&mtcp->tcp_state);
|
||||||
|
free(mtcp);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
MICROTCP_DEBUG_LOG("Instanciated ("
|
||||||
|
"debug="
|
||||||
|
#ifdef MICROTCP_DEBUG
|
||||||
|
"yes"
|
||||||
|
#else
|
||||||
|
"no"
|
||||||
|
#endif
|
||||||
|
", thread="
|
||||||
|
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||||
|
"yes"
|
||||||
|
#else
|
||||||
|
"no"
|
||||||
|
#endif
|
||||||
|
")");
|
||||||
|
|
||||||
|
return mtcp;
|
||||||
|
}
|
||||||
|
|
||||||
|
void microtcp_destroy(microtcp_t *mtcp)
|
||||||
|
{
|
||||||
|
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||||
|
MICROTCP_DEBUG_LOG("Stopping thread");
|
||||||
|
mtcp->thread_should_stop = true;
|
||||||
|
pthread_join(mtcp->thread_id, NULL);
|
||||||
|
pthread_mutex_destroy(&mtcp->lock);
|
||||||
|
MICROTCP_DEBUG_LOG("Thread stopped");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ip_free(&mtcp->ip_state);
|
||||||
|
arp_free(&mtcp->arp_state);
|
||||||
|
tcp_free(&mtcp->tcp_state);
|
||||||
|
|
||||||
|
if (mtcp->callbacks.free)
|
||||||
|
mtcp->callbacks.free(mtcp->callbacks.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static microtcp_socket_t*
|
||||||
|
pop_socket_struct_from_free_list(microtcp_t *mtcp)
|
||||||
|
{
|
||||||
|
microtcp_socket_t *socket = mtcp->free_socket_list;
|
||||||
|
mtcp->free_socket_list = socket->next;
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
have_unused_socket_structs(microtcp_t *mtcp)
|
||||||
|
{
|
||||||
|
return mtcp->free_socket_list != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
push_unlinked_socket_into_used_list(microtcp_socket_t *socket)
|
||||||
|
{
|
||||||
|
microtcp_t *mtcp = socket->mtcp;
|
||||||
|
|
||||||
|
socket->next = mtcp->used_socket_list;
|
||||||
|
if (mtcp->used_socket_list)
|
||||||
|
mtcp->used_socket_list->prev = socket;
|
||||||
|
mtcp->used_socket_list = socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
unlink_socket_from_used_socket_list(microtcp_socket_t *socket)
|
||||||
|
{
|
||||||
|
microtcp_t *mtcp = socket->mtcp;
|
||||||
|
|
||||||
|
if (socket->prev)
|
||||||
|
socket->prev->next = socket->next;
|
||||||
|
else
|
||||||
|
mtcp->used_socket_list = socket->next;
|
||||||
|
|
||||||
|
if (socket->next)
|
||||||
|
socket->next->prev = socket->prev;
|
||||||
|
|
||||||
|
socket->prev = NULL;
|
||||||
|
socket->next = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
push_unlinked_socket_into_free_list(microtcp_t *mtcp, microtcp_socket_t *socket)
|
||||||
|
{
|
||||||
|
socket->prev = NULL;
|
||||||
|
socket->next = mtcp->free_socket_list;
|
||||||
|
mtcp->free_socket_list = socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ready_to_accept(void *data)
|
||||||
|
{
|
||||||
|
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||||
|
microtcp_socket_t *socket = data;
|
||||||
|
pthread_cond_signal(&socket->something_to_accept);
|
||||||
|
#else
|
||||||
|
(void) data;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
microtcp_socket_t *microtcp_open(microtcp_t *mtcp, uint16_t port,
|
||||||
|
microtcp_errcode_t *errcode)
|
||||||
|
{
|
||||||
|
microtcp_errcode_t errcode2 = MICROTCP_ERRCODE_NONE;
|
||||||
|
microtcp_socket_t *socket = NULL;
|
||||||
|
LOCK_WHEN_THREADED(mtcp);
|
||||||
|
{
|
||||||
|
socket = pop_socket_struct_from_free_list(mtcp);
|
||||||
|
if (!socket) {
|
||||||
|
errcode2 = MICROTCP_ERRCODE_SOCKETLIMIT;
|
||||||
|
goto unlock_and_exit; // Socket limit reached
|
||||||
|
}
|
||||||
|
|
||||||
|
tcp_listener_t *listener = tcp_listener_create(&mtcp->tcp_state, port, socket, ready_to_accept);
|
||||||
|
if (listener == NULL) {
|
||||||
|
#warning "This error code should be more specific, but the TCP module isn't stable yet"
|
||||||
|
errcode2 = MICROTCP_ERRCODE_TCPERROR;
|
||||||
|
push_unlinked_socket_into_free_list(mtcp, socket);
|
||||||
|
goto unlock_and_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket->mtcp = mtcp;
|
||||||
|
socket->prev = NULL;
|
||||||
|
socket->next = NULL;
|
||||||
|
socket->type = SOCKET_LISTENER;
|
||||||
|
socket->listener = listener;
|
||||||
|
|
||||||
|
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||||
|
if (pthread_cond_init(&socket->something_to_accept, NULL)) {
|
||||||
|
errcode2 = MICROTCP_ERRCODE_BADCONDVAR;
|
||||||
|
push_unlinked_socket_into_free_list(mtcp, socket);
|
||||||
|
tcp_listener_destroy(listener);
|
||||||
|
goto unlock_and_exit;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
push_unlinked_socket_into_used_list(socket);
|
||||||
|
}
|
||||||
|
unlock_and_exit:
|
||||||
|
UNLOCK_WHEN_THREADED(mtcp);
|
||||||
|
if (errcode)
|
||||||
|
*errcode = errcode2;
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
void microtcp_close(microtcp_socket_t *socket)
|
||||||
|
{
|
||||||
|
if (!socket)
|
||||||
|
return;
|
||||||
|
|
||||||
|
microtcp_t *mtcp = socket->mtcp;
|
||||||
|
|
||||||
|
LOCK_WHEN_THREADED(mtcp);
|
||||||
|
{
|
||||||
|
switch (socket->type) {
|
||||||
|
|
||||||
|
case SOCKET_LISTENER:
|
||||||
|
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||||
|
pthread_cond_destroy(&socket->something_to_accept);
|
||||||
|
#endif
|
||||||
|
tcp_listener_destroy(socket->listener);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SOCKET_CONNECTION:
|
||||||
|
tcp_connection_destroy(socket->connection);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
unlink_socket_from_used_socket_list(socket);
|
||||||
|
push_unlinked_socket_into_free_list(mtcp, socket);
|
||||||
|
}
|
||||||
|
UNLOCK_WHEN_THREADED(mtcp);
|
||||||
|
}
|
||||||
|
|
||||||
|
microtcp_socket_t *microtcp_accept(microtcp_socket_t *socket,
|
||||||
|
bool no_block,
|
||||||
|
microtcp_errcode_t *errcode)
|
||||||
|
{
|
||||||
|
microtcp_errcode_t errcode2 = MICROTCP_ERRCODE_NONE;
|
||||||
|
microtcp_t *mtcp = socket->mtcp;
|
||||||
|
microtcp_socket_t *socket2 = NULL;
|
||||||
|
|
||||||
|
LOCK_WHEN_THREADED(mtcp);
|
||||||
|
{
|
||||||
|
if (socket->type != SOCKET_LISTENER) {
|
||||||
|
errcode2 = MICROTCP_ERRCODE_NOTLISTENER;
|
||||||
|
goto unlock_and_exit; // Can't accept from a non-listening socket
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!have_unused_socket_structs(mtcp)) {
|
||||||
|
errcode2 = MICROTCP_ERRCODE_SOCKETLIMIT;
|
||||||
|
goto unlock_and_exit; // Socket limit reached
|
||||||
|
}
|
||||||
|
|
||||||
|
tcp_connection_t *connection = tcp_listener_accept(socket->listener);
|
||||||
|
|
||||||
|
if (!connection) {
|
||||||
|
|
||||||
|
#ifdef MICROTCP_BACKGROUND_THREAD
|
||||||
|
|
||||||
|
if (no_block) {
|
||||||
|
errcode2 = MICROTCP_ERRCODE_NOTHINGTOACCEPT;
|
||||||
|
goto unlock_and_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
pthread_cond_wait(&socket->something_to_accept, &mtcp->lock);
|
||||||
|
connection = tcp_listener_accept(socket->listener);
|
||||||
|
} while (!connection);
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
if (!no_block)
|
||||||
|
errcode2 = MICROTCP_ERRCODE_CANTBLOCK;
|
||||||
|
else
|
||||||
|
errcode2 = MICROTCP_ERRCODE_NOTHINGTOACCEPT;
|
||||||
|
goto unlock_and_exit;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
socket2 = pop_socket_struct_from_free_list(mtcp);
|
||||||
|
assert(socket2); // Because we checked at the start
|
||||||
|
|
||||||
|
socket2->mtcp = mtcp;
|
||||||
|
socket2->prev = NULL;
|
||||||
|
socket2->next = NULL;
|
||||||
|
socket2->type = SOCKET_CONNECTION;
|
||||||
|
socket2->connection = connection;
|
||||||
|
|
||||||
|
push_unlinked_socket_into_used_list(socket2);
|
||||||
|
}
|
||||||
|
|
||||||
|
unlock_and_exit:
|
||||||
|
UNLOCK_WHEN_THREADED(mtcp);
|
||||||
|
|
||||||
|
if (errcode)
|
||||||
|
*errcode = errcode2;
|
||||||
|
|
||||||
|
return socket2;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t microtcp_recv(microtcp_socket_t *socket,
|
||||||
|
void *dst, size_t len,
|
||||||
|
microtcp_errcode_t *errcode)
|
||||||
|
{
|
||||||
|
if (!socket || socket->type != SOCKET_CONNECTION) {
|
||||||
|
if (errcode)
|
||||||
|
*errcode = MICROTCP_ERRCODE_NOTCONNECTION;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
microtcp_t *mtcp = socket->mtcp;
|
||||||
|
|
||||||
|
LOCK_WHEN_THREADED(mtcp);
|
||||||
|
size_t num = tcp_connection_recv(socket->connection, dst, len);
|
||||||
|
UNLOCK_WHEN_THREADED(mtcp);
|
||||||
|
|
||||||
|
if (errcode)
|
||||||
|
*errcode = MICROTCP_ERRCODE_NONE;
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t microtcp_send(microtcp_socket_t *socket,
|
||||||
|
const void *src, size_t len,
|
||||||
|
microtcp_errcode_t *errcode)
|
||||||
|
{
|
||||||
|
if (!socket || socket->type != SOCKET_CONNECTION) {
|
||||||
|
if (errcode)
|
||||||
|
*errcode = MICROTCP_ERRCODE_NOTCONNECTION;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
microtcp_t *mtcp = socket->mtcp;
|
||||||
|
|
||||||
|
LOCK_WHEN_THREADED(mtcp);
|
||||||
|
size_t num = tcp_connection_send(socket->connection, src, len);
|
||||||
|
UNLOCK_WHEN_THREADED(mtcp);
|
||||||
|
|
||||||
|
if (errcode)
|
||||||
|
*errcode = MICROTCP_ERRCODE_NONE;
|
||||||
|
return num;
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
#ifdef MICROTCP_LINUX
|
||||||
|
|
||||||
|
#include <poll.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <linux/if.h>
|
||||||
|
#include <linux/if_tun.h>
|
||||||
|
#include <net/if_arp.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
|
#include "defs.h"
|
||||||
|
#include "microtcp.h"
|
||||||
|
|
||||||
|
static int systemf(const char *fmt, ...)
|
||||||
|
{
|
||||||
|
char buffer[256];
|
||||||
|
|
||||||
|
va_list va;
|
||||||
|
va_start(va, fmt);
|
||||||
|
vsnprintf(buffer, sizeof(buffer), fmt, va);
|
||||||
|
va_end(va);
|
||||||
|
|
||||||
|
fprintf(stderr, "INFO: Running [%s]\n", buffer);
|
||||||
|
|
||||||
|
return system(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int create_tun_device(const char *dev, char dev2[IFNAMSIZ])
|
||||||
|
{
|
||||||
|
int fd = open("/dev/net/tun", O_RDWR);
|
||||||
|
if (fd < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
struct ifreq ifr;
|
||||||
|
memset(&ifr, 0, sizeof(struct ifreq));
|
||||||
|
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
|
||||||
|
|
||||||
|
if (dev && *dev)
|
||||||
|
strncpy(ifr.ifr_name, dev, IFNAMSIZ);
|
||||||
|
|
||||||
|
int err = ioctl(fd, TUNSETIFF, &ifr);
|
||||||
|
if (err) {
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
strncpy(dev2, ifr.ifr_name, IFNAMSIZ);
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int send_callback(void *data, const void *src, size_t len)
|
||||||
|
{
|
||||||
|
int tap_fd = (int) data;
|
||||||
|
return write(tap_fd, src, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int recv_callback(void *data, void *dst, size_t len)
|
||||||
|
{
|
||||||
|
int tap_fd = (int) data;
|
||||||
|
|
||||||
|
int timeout = 1000;
|
||||||
|
|
||||||
|
struct pollfd pfd = {.fd=tap_fd, .events=POLLIN};
|
||||||
|
int status = poll(&pfd, 1, timeout);
|
||||||
|
if (status < 1) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
int num = read(tap_fd, dst, len);
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void free_callback(void *data)
|
||||||
|
{
|
||||||
|
int tap_fd = (int) data;
|
||||||
|
close(tap_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
static bool get_ip_address(const char *dev, microtcp_ip_t *ip)
|
||||||
|
{
|
||||||
|
int fd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||||
|
if (fd < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
struct ifreq ifr;
|
||||||
|
memset(&ifr, 0, sizeof(struct ifreq));
|
||||||
|
ifr.ifr_addr.sa_family = AF_INET;
|
||||||
|
strncpy(ifr.ifr_name, dev, IFNAMSIZ-1);
|
||||||
|
|
||||||
|
ioctl(fd, SIOCGIFADDR, &ifr);
|
||||||
|
|
||||||
|
memcpy(ip, &((struct sockaddr_in*) &ifr.ifr_addr)->sin_addr, sizeof(microtcp_ip_t));
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
static bool get_mac_address(const char *dev, microtcp_mac_t *mac)
|
||||||
|
{
|
||||||
|
int fd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||||
|
if (fd < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
struct ifreq ifr;
|
||||||
|
memset(&ifr, 0, sizeof(struct ifreq));
|
||||||
|
strncpy(ifr.ifr_name, dev, IFNAMSIZ-1);
|
||||||
|
|
||||||
|
ioctl(fd, SIOCGIFHWADDR, &ifr);
|
||||||
|
|
||||||
|
if (ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
memcpy(mac, ifr.ifr_hwaddr.sa_data, sizeof(microtcp_mac_t));
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
microtcp_t *microtcp_create()
|
||||||
|
{
|
||||||
|
char dev[IFNAMSIZ];
|
||||||
|
int tap_fd = create_tun_device("tap0", dev);
|
||||||
|
if (tap_fd < 0) {
|
||||||
|
fprintf(stderr, "ERROR: Failed creating TAP device (%s)\n", strerror(errno));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "INFO: Using TAP device %s\n", dev);
|
||||||
|
|
||||||
|
const char *tap_addr = "10.0.0.5";
|
||||||
|
const char *tap_route = "10.0.0.0/24";
|
||||||
|
|
||||||
|
systemf("ip link set dev %s up", dev);
|
||||||
|
systemf("ip route add dev %s %s", dev, tap_route);
|
||||||
|
systemf("ip address add dev %s local %s", dev, tap_addr);
|
||||||
|
systemf("sudo sysctl -w net.ipv4.ip_forward=1");
|
||||||
|
|
||||||
|
microtcp_mac_t mac;
|
||||||
|
if (!get_mac_address(dev, &mac)) {
|
||||||
|
fprintf(stderr, "FATAL: Failed to query NIC for IP or MAC (%s)\n", strerror(errno));
|
||||||
|
close(tap_fd);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
mac.data[5]++;
|
||||||
|
|
||||||
|
microtcp_ip_t ip;
|
||||||
|
inet_pton(AF_INET, "10.0.0.4", &ip);
|
||||||
|
|
||||||
|
struct in_addr ip2;
|
||||||
|
ip2.s_addr = ip;
|
||||||
|
fprintf(stderr, "INFO: Using IP %s\n", inet_ntoa(ip2));
|
||||||
|
fprintf(stderr, "INFO: Using MAC %x:%x:%x:%x:%x:%x\n", mac.data[0], mac.data[1], mac.data[2], mac.data[3], mac.data[4], mac.data[5]);
|
||||||
|
|
||||||
|
microtcp_callbacks_t callbacks = {
|
||||||
|
.data = (int) tap_fd,
|
||||||
|
.send = send_callback,
|
||||||
|
.recv = recv_callback,
|
||||||
|
.free = free_callback,
|
||||||
|
};
|
||||||
|
|
||||||
|
microtcp_t *mtcp = microtcp_create_using_callbacks(ip, mac, callbacks);
|
||||||
|
if (!mtcp) {
|
||||||
|
close(tap_fd);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mtcp;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* MICROTCP_LINUX */
|
||||||
@@ -0,0 +1,468 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include "tcp.h"
|
||||||
|
|
||||||
|
#ifdef TCP_DEBUG
|
||||||
|
#include <stdio.h>
|
||||||
|
#define TCP_DEBUG_LOG(fmt, ...) fprintf(stderr, "TCP :: " fmt "\n", ## __VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define TCP_DEBUG_LOG(...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int tcp_send(tcp_state_t *tcp_state, ip_address_t ip,
|
||||||
|
const void *src, size_t len)
|
||||||
|
{
|
||||||
|
return tcp_state->callbacks.send(tcp_state->callbacks.data, ip, src, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tcp_init(tcp_state_t *tcp_state, tcp_callbacks_t callbacks)
|
||||||
|
{
|
||||||
|
tcp_state->callbacks = callbacks;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < TCP_MAX_SOCKETS-1; i++)
|
||||||
|
tcp_state->connection_pool[i].next = tcp_state->connection_pool + i+1;
|
||||||
|
tcp_state->connection_pool[TCP_MAX_SOCKETS-1].next = NULL;
|
||||||
|
tcp_state->free_connection_list = tcp_state->connection_pool;
|
||||||
|
tcp_state->used_connection_list = NULL;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < TCP_MAX_LISTENERS-1; i++)
|
||||||
|
tcp_state->listener_pool[i].next = tcp_state->listener_pool + i+1;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tcp_free(tcp_state_t *tcp_state)
|
||||||
|
{
|
||||||
|
// Destroy all listening connections
|
||||||
|
while (tcp_state->used_listener_list != NULL)
|
||||||
|
tcp_listener_destroy(tcp_state->used_listener_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tcp_seconds_passed(tcp_state_t *state, size_t seconds)
|
||||||
|
{
|
||||||
|
(void) state;
|
||||||
|
(void) seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
static tcp_connection_t*
|
||||||
|
connection_create_waiting_for_ack(tcp_listener_t *listener,
|
||||||
|
uint32_t seq_no, uint32_t ack_no,
|
||||||
|
ip_address_t peer_ip, uint16_t peer_port)
|
||||||
|
{
|
||||||
|
tcp_state_t *state = listener->state;
|
||||||
|
|
||||||
|
// Pop a connection structure from the free list
|
||||||
|
if (state->free_connection_list == NULL)
|
||||||
|
// ERROR: Reached connection limit
|
||||||
|
return NULL;
|
||||||
|
tcp_connection_t *connection = state->free_connection_list;
|
||||||
|
state->free_connection_list = connection->next;
|
||||||
|
|
||||||
|
// Initialize connection structure
|
||||||
|
connection->listener = listener;
|
||||||
|
connection->seq_no = seq_no;
|
||||||
|
connection->ack_no = ack_no;
|
||||||
|
connection->peer_port = peer_port;
|
||||||
|
connection->peer_ip = peer_ip;
|
||||||
|
connection->in_used = 0;
|
||||||
|
connection->out_used = 0;
|
||||||
|
connection->prev = NULL;
|
||||||
|
connection->next = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
// Append the connection to the list of connections
|
||||||
|
// waiting for the ACK message
|
||||||
|
if (listener->connections_waiting_for_ack)
|
||||||
|
listener->connections_waiting_for_ack->prev = connection;
|
||||||
|
connection->next = listener->connections_waiting_for_ack;
|
||||||
|
listener->connections_waiting_for_ack = connection;
|
||||||
|
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
static tcp_listener_t*
|
||||||
|
find_listener_with_port(tcp_state_t *state, uint16_t port)
|
||||||
|
{
|
||||||
|
TCP_DEBUG_LOG("Looking for listener with port %d", port);
|
||||||
|
|
||||||
|
tcp_listener_t *cursor = state->used_listener_list;
|
||||||
|
while (cursor) {
|
||||||
|
TCP_DEBUG_LOG("port=%d, seeking=%d", cursor->port, port);
|
||||||
|
if (cursor->port == port)
|
||||||
|
return cursor;
|
||||||
|
cursor = cursor->next;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SOCKET_WAIT,
|
||||||
|
SOCKET_IDLE,
|
||||||
|
SOCKET_NONE,
|
||||||
|
} connection_state_t;
|
||||||
|
|
||||||
|
static tcp_connection_t *find_connection(tcp_connection_t *list, ip_address_t peer_ip, uint16_t peer_port)
|
||||||
|
{
|
||||||
|
tcp_connection_t *cursor = list;
|
||||||
|
|
||||||
|
while (cursor) {
|
||||||
|
if (cursor->peer_ip == peer_ip && cursor->peer_port == peer_port)
|
||||||
|
return cursor;
|
||||||
|
cursor = cursor->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static connection_state_t find_connection_associated_to(tcp_listener_t *listener, ip_address_t peer_ip, uint16_t peer_port, tcp_connection_t **connection)
|
||||||
|
{
|
||||||
|
tcp_connection_t *connection2 = find_connection(listener->connections, peer_ip, peer_port);
|
||||||
|
if (connection2) {
|
||||||
|
*connection = connection2;
|
||||||
|
return SOCKET_IDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
connection2 = find_connection(listener->connections_waiting_for_accept_head, peer_ip, peer_port);
|
||||||
|
if (connection2) {
|
||||||
|
*connection = connection2;
|
||||||
|
return SOCKET_IDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
connection2 = find_connection(listener->connections_waiting_for_ack, peer_ip, peer_port);
|
||||||
|
if (connection2) {
|
||||||
|
*connection = connection2;
|
||||||
|
return SOCKET_WAIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
*connection = NULL;
|
||||||
|
return SOCKET_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t choose_ack()
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tcp_process_segment(tcp_state_t *state, ip_address_t sender,
|
||||||
|
tcp_segment_t *segment, size_t len)
|
||||||
|
{
|
||||||
|
(void) state;
|
||||||
|
(void) sender;
|
||||||
|
(void) segment;
|
||||||
|
(void) len;
|
||||||
|
|
||||||
|
TCP_DEBUG_LOG("Received TCP segment");
|
||||||
|
|
||||||
|
if (len < sizeof(tcp_segment_t))
|
||||||
|
return;
|
||||||
|
|
||||||
|
uint16_t reordered_dst_port = ntohs(segment->dst_port);
|
||||||
|
uint16_t reordered_src_port = ntohs(segment->src_port);
|
||||||
|
|
||||||
|
tcp_listener_t *listener = find_listener_with_port(state, reordered_dst_port);
|
||||||
|
if (listener == NULL) {
|
||||||
|
// No connection is listening on this port. Silently drop the packet.
|
||||||
|
TCP_DEBUG_LOG("Segment sent to port %d, which is closed", reordered_dst_port);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tcp_connection_t *connection;
|
||||||
|
connection_state_t connection_state = find_connection_associated_to(listener, sender, reordered_src_port, &connection);
|
||||||
|
|
||||||
|
if ((segment->flags & TCP_FLAG_SYN) && !(segment->flags & TCP_FLAG_ACK)) {
|
||||||
|
|
||||||
|
/* Connection request */
|
||||||
|
|
||||||
|
if (connection_state != SOCKET_NONE) {
|
||||||
|
// Peer wants to connect, but a connection was already created..
|
||||||
|
// What to do?
|
||||||
|
#warning "TODO: Handle case where an existing connection recieved the SYN message"
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temporary
|
||||||
|
uint32_t seq_no = ntohs(segment->seq_no);
|
||||||
|
uint32_t ack_no = choose_ack();
|
||||||
|
|
||||||
|
tcp_connection_t *connection = connection_create_waiting_for_ack(listener, seq_no, ack_no, sender, reordered_dst_port);
|
||||||
|
if (connection == NULL) {
|
||||||
|
// ERROR: Socket limit reached. Drop the connection silently
|
||||||
|
#warning "TODO: Handle connection limit reached (RST?)"
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tcp_segment_t segment2 = {
|
||||||
|
.src_port = segment->dst_port,
|
||||||
|
.dst_port = segment->src_port,
|
||||||
|
.flags = TCP_FLAG_SYN | TCP_FLAG_ACK, // No need for fixing endianess, it's just one byte!
|
||||||
|
.seq_no = htons(ack_no),
|
||||||
|
.ack_no = htons(seq_no),
|
||||||
|
.offset = 5,
|
||||||
|
.unused = 0,
|
||||||
|
.window = htons(TCP_INPUT_BUFFER_SIZE - connection->in_used),
|
||||||
|
.checksum = 0,
|
||||||
|
.urgent_pointer = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
#warning "TODO: Calculare checksum"
|
||||||
|
|
||||||
|
tcp_send(state, sender, &segment2, sizeof(segment2));
|
||||||
|
|
||||||
|
#warning "TODO: Handle TCP connection creation"
|
||||||
|
|
||||||
|
} else if ((segment->flags & TCP_FLAG_SYN) && (segment->flags & TCP_FLAG_ACK)) {
|
||||||
|
|
||||||
|
// Drop the packet. We only do servers for now!
|
||||||
|
#warning "TODO: Handle TCP second message of three way handshake"
|
||||||
|
|
||||||
|
} else if (!(segment->flags & TCP_FLAG_SYN) && (segment->flags & TCP_FLAG_ACK)) {
|
||||||
|
|
||||||
|
if (connection_state != SOCKET_WAIT) {
|
||||||
|
// Either there is no connection or the connection wasn't
|
||||||
|
// waiting for an ACK segment. What to do?
|
||||||
|
#warning "TODO: Handle case where an existing connection recieved the SYN message"
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move connection from the waiting-for-ack list
|
||||||
|
// to the waiting-for-accept queue.
|
||||||
|
|
||||||
|
// Unlink it from the current list
|
||||||
|
{
|
||||||
|
if (connection->prev)
|
||||||
|
connection->prev->next = connection->next;
|
||||||
|
else
|
||||||
|
listener->connections_waiting_for_ack = connection->next;
|
||||||
|
|
||||||
|
if (connection->next)
|
||||||
|
connection->next->prev = connection->prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push it to the new one
|
||||||
|
{
|
||||||
|
connection->prev = NULL;
|
||||||
|
connection->next = listener->connections_waiting_for_accept_head;
|
||||||
|
|
||||||
|
if (listener->connections_waiting_for_accept_head)
|
||||||
|
// Accept queue isn't empty
|
||||||
|
listener->connections_waiting_for_accept_head->prev = connection;
|
||||||
|
else
|
||||||
|
// Accept queue is empty
|
||||||
|
listener->connections_waiting_for_accept_tail = connection;
|
||||||
|
listener->connections_waiting_for_accept_head = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listener->callback)
|
||||||
|
listener->callback(listener->data);
|
||||||
|
|
||||||
|
// TODO: What about the payload?
|
||||||
|
#warning "TODO: Handle payload of TCP ACK message"
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (connection_state != SOCKET_IDLE) {
|
||||||
|
// Either there is no connection associated to this peer
|
||||||
|
// at this port, or the connection is waiting for an ACK.
|
||||||
|
// What to do?
|
||||||
|
#warning "TODO: Handle case where unexpected TCP data message is received"
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *payload = segment->payload;
|
||||||
|
size_t payload_arrived = len - sizeof(tcp_segment_t);
|
||||||
|
size_t payload_capacity = TCP_INPUT_BUFFER_SIZE - connection->in_used;
|
||||||
|
size_t payload_considered = MIN(payload_arrived, payload_capacity);
|
||||||
|
|
||||||
|
memcpy(connection->in_buffer + connection->in_used, payload, payload_considered);
|
||||||
|
connection->in_used += payload_considered;
|
||||||
|
connection->ack_no += payload_considered; // Is this right?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tcp_listener_t*
|
||||||
|
tcp_listener_create(tcp_state_t *state, uint16_t port, void *data, void (*callback)(void*))
|
||||||
|
{
|
||||||
|
if (find_listener_with_port(state, port)) {
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
tcp_listener_t *listener = state->free_listener_list;
|
||||||
|
state->free_listener_list = listener->next;
|
||||||
|
|
||||||
|
// Initialize listener structure
|
||||||
|
listener->state = state;
|
||||||
|
listener->port = port;
|
||||||
|
listener->connections = NULL;
|
||||||
|
listener->connections_waiting_for_ack = NULL;
|
||||||
|
listener->connections_waiting_for_accept_head = NULL;
|
||||||
|
listener->connections_waiting_for_accept_tail = NULL;
|
||||||
|
listener->data = data;
|
||||||
|
listener->callback = callback;
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
tcp_connection_t *tcp_listener_accept(tcp_listener_t *listener)
|
||||||
|
{
|
||||||
|
(void) listener;
|
||||||
|
|
||||||
|
if (!listener->connections_waiting_for_accept_tail)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
tcp_connection_t *connection;
|
||||||
|
|
||||||
|
// Pop connection from the accept queue
|
||||||
|
{
|
||||||
|
connection = listener->connections_waiting_for_accept_tail;
|
||||||
|
if (connection->prev)
|
||||||
|
connection->prev->next = NULL;
|
||||||
|
else
|
||||||
|
listener->connections_waiting_for_accept_head = NULL;
|
||||||
|
listener->connections_waiting_for_accept_tail = connection->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push it to idle list
|
||||||
|
{
|
||||||
|
connection->prev = NULL;
|
||||||
|
connection->next = listener->connections;
|
||||||
|
|
||||||
|
if (listener->connections)
|
||||||
|
listener->connections->prev = connection;
|
||||||
|
listener->connections = connection;
|
||||||
|
}
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tcp_connection_destroy(tcp_connection_t *connection)
|
||||||
|
{
|
||||||
|
// NOTE: This can only be called when the
|
||||||
|
// connection was accepted.
|
||||||
|
|
||||||
|
tcp_listener_t *listener = connection->listener;
|
||||||
|
|
||||||
|
// Pop connection from the idle connection list
|
||||||
|
if (connection->prev)
|
||||||
|
connection->prev->next = connection->next;
|
||||||
|
else
|
||||||
|
listener->connections = connection->next;
|
||||||
|
|
||||||
|
// Push it into the free connection list
|
||||||
|
tcp_state_t *state = listener->state;
|
||||||
|
|
||||||
|
connection->prev = NULL;
|
||||||
|
connection->next = state->free_connection_list;
|
||||||
|
state->free_connection_list = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t tcp_connection_recv(tcp_connection_t *connection,
|
||||||
|
void *dst, size_t len)
|
||||||
|
{
|
||||||
|
size_t num = MIN(len, connection->in_used);
|
||||||
|
memcpy(dst, connection->in_buffer, num);
|
||||||
|
|
||||||
|
memmove(connection->in_buffer, connection->in_buffer + num, connection->in_used - num);
|
||||||
|
connection->in_used -= num;
|
||||||
|
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
append_to_output_buffer(tcp_connection_t *connection,
|
||||||
|
const void *src, size_t len)
|
||||||
|
{
|
||||||
|
size_t num = MIN(len, TCP_OUTPUT_BUFFER_SIZE - connection->out_used);
|
||||||
|
|
||||||
|
memcpy(connection->out_buffer + connection->out_used, src, num);
|
||||||
|
connection->out_used += len;
|
||||||
|
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t calculate_checksum(const void *data, size_t size)
|
||||||
|
{
|
||||||
|
(void) data,
|
||||||
|
(void) size;
|
||||||
|
|
||||||
|
#warning "TODO: Calculate TCP checksum"
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
try_flushing_output_buffer(tcp_connection_t *connection)
|
||||||
|
{
|
||||||
|
tcp_state_t *tcp_state = connection->listener->state;
|
||||||
|
|
||||||
|
tcp_segment_t *segment = &connection->out_header;
|
||||||
|
segment->src_port = htons(connection->listener->port);
|
||||||
|
segment->dst_port = htons(connection->peer_port);
|
||||||
|
segment->seq_no = htons(connection->seq_no); // Should this be increased by the segment size?
|
||||||
|
segment->ack_no = htons(connection->ack_no);
|
||||||
|
segment->unused = 0;
|
||||||
|
segment->offset = 5; // No options
|
||||||
|
segment->flags = 0;
|
||||||
|
segment->window = htons(TCP_INPUT_BUFFER_SIZE - connection->in_used);
|
||||||
|
segment->checksum = 0; // Temporary value
|
||||||
|
segment->urgent_pointer = 0; // Don't support urgent data
|
||||||
|
|
||||||
|
segment->checksum = calculate_checksum(segment, sizeof(tcp_segment_t));
|
||||||
|
|
||||||
|
int sent_bytes = tcp_send(tcp_state, connection->peer_ip, segment, sizeof(tcp_segment_t) + connection->out_used);
|
||||||
|
|
||||||
|
if (sent_bytes < 0) {
|
||||||
|
// It wasn't possible to send out bytes. We'll try again later!
|
||||||
|
} else {
|
||||||
|
memmove(connection->out_buffer, connection->out_buffer + sent_bytes, connection->out_used - sent_bytes);
|
||||||
|
connection->out_used -= sent_bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t tcp_connection_send(tcp_connection_t *connection, const void *src, size_t len)
|
||||||
|
{
|
||||||
|
size_t num = append_to_output_buffer(connection, src, len);
|
||||||
|
try_flushing_output_buffer(connection);
|
||||||
|
return num;
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <endian.h>
|
||||||
|
#include "defs.h"
|
||||||
|
|
||||||
|
#define TCP_MAX_LISTENERS 32
|
||||||
|
#define TCP_MAX_SOCKETS 1024
|
||||||
|
#define TCP_INPUT_BUFFER_SIZE 1024
|
||||||
|
#define TCP_OUTPUT_BUFFER_SIZE 1024
|
||||||
|
|
||||||
|
typedef struct tcp_state_t tcp_state_t; // Predeclare for cyclic references
|
||||||
|
|
||||||
|
#define TCP_FLAG_FIN 0x01
|
||||||
|
#define TCP_FLAG_SYN 0x02
|
||||||
|
#define TCP_FLAG_RST 0x04
|
||||||
|
#define TCP_FLAG_PUSH 0x08
|
||||||
|
#define TCP_FLAG_ACK 0x10
|
||||||
|
#define TCP_FLAG_URG 0x20
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint16_t src_port;
|
||||||
|
uint16_t dst_port;
|
||||||
|
uint32_t seq_no;
|
||||||
|
uint32_t ack_no;
|
||||||
|
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||||
|
uint8_t unused: 4;
|
||||||
|
uint8_t offset: 4;
|
||||||
|
#endif
|
||||||
|
#if __BYTE_ORDER == __BIG_ENDIAN
|
||||||
|
uint8_t offset: 4;
|
||||||
|
uint8_t unused: 4;
|
||||||
|
#endif
|
||||||
|
uint8_t flags;
|
||||||
|
uint16_t window;
|
||||||
|
uint16_t checksum;
|
||||||
|
uint16_t urgent_pointer;
|
||||||
|
char payload[];
|
||||||
|
} tcp_segment_t;
|
||||||
|
|
||||||
|
typedef struct tcp_connection_t tcp_connection_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;
|
||||||
|
uint16_t port;
|
||||||
|
tcp_connection_t *connections;
|
||||||
|
tcp_connection_t *connections_waiting_for_ack;
|
||||||
|
tcp_connection_t *connections_waiting_for_accept_head;
|
||||||
|
tcp_connection_t *connections_waiting_for_accept_tail;
|
||||||
|
void (*callback)(void*);
|
||||||
|
void *data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct tcp_connection_t {
|
||||||
|
tcp_listener_t *listener; // Listener that accepted this connection
|
||||||
|
tcp_connection_t *next;
|
||||||
|
tcp_connection_t *prev;
|
||||||
|
ip_address_t peer_ip;
|
||||||
|
uint16_t peer_port;
|
||||||
|
uint32_t seq_no;
|
||||||
|
uint32_t ack_no;
|
||||||
|
size_t in_used;
|
||||||
|
size_t out_used;
|
||||||
|
tcp_segment_t out_header; // There must be no padding between
|
||||||
|
char out_buffer[TCP_OUTPUT_BUFFER_SIZE]; // these two
|
||||||
|
char in_buffer[TCP_INPUT_BUFFER_SIZE];
|
||||||
|
};
|
||||||
|
static_assert(offsetof(tcp_connection_t, out_buffer) == offsetof(tcp_connection_t, out_header) + sizeof(tcp_segment_t));
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
void *data;
|
||||||
|
int (*send)(void *data, ip_address_t ip, const void *src, size_t len);
|
||||||
|
} tcp_callbacks_t;
|
||||||
|
|
||||||
|
struct tcp_state_t {
|
||||||
|
tcp_callbacks_t callbacks;
|
||||||
|
tcp_connection_t *used_connection_list;
|
||||||
|
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];
|
||||||
|
};
|
||||||
|
|
||||||
|
void tcp_init(tcp_state_t *tcp_state, 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*));
|
||||||
|
void tcp_listener_destroy(tcp_listener_t *listener);
|
||||||
|
tcp_connection_t *tcp_listener_accept(tcp_listener_t *listener);
|
||||||
|
void tcp_connection_destroy(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);
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <microtcp.h>
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
microtcp_errcode_t errcode;
|
||||||
|
|
||||||
|
microtcp_t *mtcp = microtcp_create();
|
||||||
|
if (mtcp == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
uint16_t port = 80;
|
||||||
|
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);
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
fprintf(stderr, "About to accept\n");
|
||||||
|
microtcp_socket_t *client = microtcp_accept(server, false, &errcode);
|
||||||
|
if (errcode && errcode != MICROTCP_ERRCODE_NOTHINGTOACCEPT) {
|
||||||
|
fprintf(stderr, "Error: %s\n", microtcp_strerror(errcode));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "Accepted a connection\n");
|
||||||
|
|
||||||
|
char buffer[1024];
|
||||||
|
size_t num = microtcp_recv(client, buffer, sizeof(buffer), &errcode);
|
||||||
|
if (errcode) {
|
||||||
|
fprintf(stderr, "Error: %s\n", microtcp_strerror(errcode));
|
||||||
|
goto handled;
|
||||||
|
}
|
||||||
|
microtcp_send(client, "echo: ", 6, &errcode);
|
||||||
|
if (errcode) {
|
||||||
|
fprintf(stderr, "Error: %s\n", microtcp_strerror(errcode));
|
||||||
|
goto handled;
|
||||||
|
}
|
||||||
|
microtcp_send(client, buffer, num, &errcode);
|
||||||
|
if (errcode) {
|
||||||
|
fprintf(stderr, "Error: %s\n", microtcp_strerror(errcode));
|
||||||
|
goto handled;
|
||||||
|
}
|
||||||
|
handled:
|
||||||
|
microtcp_close(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
microtcp_close(server);
|
||||||
|
microtcp_destroy(mtcp);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+161
@@ -0,0 +1,161 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "test_arp_util.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
WHITE BOX TEST CASES
|
||||||
|
|
||||||
|
A) Peer requests host's MAC given its IP and host
|
||||||
|
replies.
|
||||||
|
|
||||||
|
B) Peer requests host's non MAC level 2 address
|
||||||
|
given its IP and host doesn't reply because it
|
||||||
|
only supports MAC.
|
||||||
|
|
||||||
|
C) Peer requests host's MAC address given its non
|
||||||
|
IP level 3 address and host doesn't reply
|
||||||
|
because it only supports IP.
|
||||||
|
|
||||||
|
D) Peer requests host's MAC given its IP, but the
|
||||||
|
MAC address length field isn't 6, therefore
|
||||||
|
host doesn't reply.
|
||||||
|
|
||||||
|
E) Peer requests host's MAC given its IP, but the
|
||||||
|
IP address length field isn't 4, therefore host
|
||||||
|
doesn't reply.
|
||||||
|
|
||||||
|
F) Peer sends a request/reply that doesn't refer
|
||||||
|
to host and is from a sender with IP never seen
|
||||||
|
by the host (no entry in the translation table).
|
||||||
|
It's expected that no entry is added to the
|
||||||
|
translation table.
|
||||||
|
|
||||||
|
G) Peer sends a request/reply that doesn't refer
|
||||||
|
to host but is from a sender with IP already
|
||||||
|
in the ARP translation table, therefore is
|
||||||
|
expected that host updates the entry.
|
||||||
|
|
||||||
|
H) Program queries the ARP module for a MAC that
|
||||||
|
isn't cached, therefore an ARP request is
|
||||||
|
expected to be generated and, when replied to,
|
||||||
|
the ARP module is expected to resolve the
|
||||||
|
program's query.
|
||||||
|
|
||||||
|
I) Program queries the ARP module for a MAC that's
|
||||||
|
cached, therefore the ARP module is expected to
|
||||||
|
resolve it without sending packets.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static arp_testcase_result_t test_000(char *msg, size_t msgmax)
|
||||||
|
{
|
||||||
|
char host_ip[4] = {0xc0, 0xa8, 0x01, 0x05};
|
||||||
|
char host_mac[6] = {0xcc, 0x6b, 0x1e, 0x13, 0xa8, 0x93};
|
||||||
|
|
||||||
|
arp_testcase_t testcase;
|
||||||
|
arp_testcase_init(&testcase, host_mac, host_ip);
|
||||||
|
|
||||||
|
arp_testcase_send(&testcase, (char[]) {
|
||||||
|
0x00, 0x01, // hardware_type=ethernet
|
||||||
|
0x08, 0x00, // protocol_type=ip
|
||||||
|
0x06, // hardware_len=6
|
||||||
|
0x04, // protocol_len=4
|
||||||
|
0x00, 0x01, // operation_type=request
|
||||||
|
0xbc, 0x15, 0xac, 0x29, 0xe5, 0x61, // sender MAC
|
||||||
|
0xc0, 0xa8, 0x01, 0x01, // sender IP
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // target MAC (empty)
|
||||||
|
0xc0, 0xa8, 0x01, 0x05, // target IP
|
||||||
|
});
|
||||||
|
|
||||||
|
arp_testcase_recv(&testcase, (char[]) {
|
||||||
|
0x00, 0x01, // hardware_type=ethernet
|
||||||
|
0x08, 0x00, // protocol_type=ip
|
||||||
|
0x06, // hardware_len=6
|
||||||
|
0x04, // protocol_len=4
|
||||||
|
0x00, 0x02, // operation_type=reply
|
||||||
|
0xcc, 0x6b, 0x1e, 0x13, 0xa8, 0x93, // sender MAC
|
||||||
|
0xc0, 0xa8, 0x01, 0x05, // sender IP
|
||||||
|
0xbc, 0x15, 0xac, 0x29, 0xe5, 0x61, // target MAC
|
||||||
|
0xc0, 0xa8, 0x01, 0x01, // target IP
|
||||||
|
});
|
||||||
|
|
||||||
|
arp_testcase_result_t result =
|
||||||
|
arp_testcase_run(testcase, msg, msgmax);
|
||||||
|
|
||||||
|
arp_testcase_free(&testcase);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static arp_testcase_result_t test_001(char *msg, size_t msgmax)
|
||||||
|
{
|
||||||
|
/* This testcase simulates the host receiving
|
||||||
|
* an ARP request not associated to it, therefore
|
||||||
|
* the ARP module is expected to not reply.
|
||||||
|
*/
|
||||||
|
|
||||||
|
char host_ip[4] = {0xc0, 0xa8, 0x01, 0x05};
|
||||||
|
char host_mac[6] = {0xcc, 0x6b, 0x1e, 0x13, 0xa8, 0x93};
|
||||||
|
|
||||||
|
arp_testcase_t testcase;
|
||||||
|
arp_testcase_init(&testcase, host_mac, host_ip);
|
||||||
|
|
||||||
|
arp_testcase_send(&testcase, (char[]) {
|
||||||
|
0x00, 0x01, // hardware_type=ethernet
|
||||||
|
0x08, 0x00, // protocol_type=ip
|
||||||
|
0x06, // hardware_len=6
|
||||||
|
0x04, // protocol_len=4
|
||||||
|
0x00, 0x01, // operation_type=request
|
||||||
|
0xbc, 0x15, 0xac, 0x29, 0xe5, 0x61, // sender MAC
|
||||||
|
0xc0, 0xa8, 0x01, 0x01, // sender IP
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // target MAC (empty)
|
||||||
|
0xc0, 0xa8, 0x01, 0x10, // target IP (different to the host's)
|
||||||
|
});
|
||||||
|
|
||||||
|
arp_testcase_result_t result =
|
||||||
|
arp_testcase_run(testcase, msg, msgmax);
|
||||||
|
|
||||||
|
arp_testcase_free(&testcase);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef arp_testcase_result_t (*arp_testcase_routine_t)(char*, size_t);
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
static const arp_testcase_routine_t routines[] = {
|
||||||
|
test_000,
|
||||||
|
test_001,
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
size_t passed = 0;
|
||||||
|
size_t failed = 0;
|
||||||
|
size_t aborted = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; routines[i]; i++) {
|
||||||
|
|
||||||
|
char message[1024];
|
||||||
|
arp_testcase_result_t result = routines[i](message, sizeof(message));
|
||||||
|
|
||||||
|
switch (result) {
|
||||||
|
case ARP_TESTCASE_PASSED:
|
||||||
|
fprintf(stdout, "Test %ld ... PASSED\n", i);
|
||||||
|
passed++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARP_TESTCASE_FAILED:
|
||||||
|
fprintf(stdout, "Test %ld ... FAILED: %s\n", i, message);
|
||||||
|
failed++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARP_TESTCASE_ABORTED:
|
||||||
|
fprintf(stdout, "Test %ld ... ABORTED: %s\n", i, message);
|
||||||
|
aborted++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stdout, "SUMMARY: %ld passed, %ld failed and %ld aborted\n",
|
||||||
|
passed, failed, aborted);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "test_arp_util.h"
|
||||||
|
|
||||||
|
void arp_testcase_init(arp_testcase_t *tcase,
|
||||||
|
const char mac[static 6],
|
||||||
|
const char ip[static 4])
|
||||||
|
{
|
||||||
|
memcpy(&tcase->self_ip, ip, 4);
|
||||||
|
memcpy(&tcase->self_mac, mac, 6);
|
||||||
|
tcase->failed = false;
|
||||||
|
tcase->count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void arp_testcase_free(arp_testcase_t *tcase)
|
||||||
|
{
|
||||||
|
(void) tcase;
|
||||||
|
}
|
||||||
|
|
||||||
|
void arp_testcase_send(arp_testcase_t *tcase, const char data[static sizeof(arp_packet_t)])
|
||||||
|
{
|
||||||
|
if (tcase->failed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (tcase->count == ARP_TESTCASE_MAX_PACKETS) {
|
||||||
|
tcase->failed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
arp_testcase_packet_t *packet = tcase->packets + tcase->count;
|
||||||
|
packet->sender = ARP_TESTCASE_SENDER_PEER;
|
||||||
|
packet->data = data;
|
||||||
|
|
||||||
|
tcase->count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void arp_testcase_recv(arp_testcase_t *tcase, const char data[static sizeof(arp_packet_t)])
|
||||||
|
{
|
||||||
|
if (tcase->failed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (tcase->count == ARP_TESTCASE_MAX_PACKETS) {
|
||||||
|
tcase->failed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
arp_testcase_packet_t *packet = tcase->packets + tcase->count;
|
||||||
|
packet->sender = ARP_TESTCASE_SENDER_HOST;
|
||||||
|
packet->data = data;
|
||||||
|
|
||||||
|
tcase->count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *msg;
|
||||||
|
size_t msgmax;
|
||||||
|
const arp_packet_t *output;
|
||||||
|
const arp_testcase_t *tcase;
|
||||||
|
size_t cursor;
|
||||||
|
bool output_as_expected;
|
||||||
|
bool aborted_while_checking_sent_packets;
|
||||||
|
} testcase_contex_t;
|
||||||
|
|
||||||
|
static void send_packet(void *data, mac_address_t dest)
|
||||||
|
{
|
||||||
|
(void) dest; // Is this ok?
|
||||||
|
|
||||||
|
testcase_contex_t *context = data;
|
||||||
|
|
||||||
|
context->aborted_while_checking_sent_packets = false;
|
||||||
|
|
||||||
|
if (context->cursor == context->tcase->count) {
|
||||||
|
snprintf(context->msg, context->msgmax, "ARP module sent an unexpected packet");
|
||||||
|
context->output_as_expected = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const arp_testcase_packet_t *packet = context->tcase->packets + context->cursor;
|
||||||
|
|
||||||
|
if (packet->sender != ARP_TESTCASE_SENDER_HOST) {
|
||||||
|
snprintf(context->msg, context->msgmax, "ARP module sent an unexpected packet");
|
||||||
|
context->output_as_expected = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
context->cursor++;
|
||||||
|
|
||||||
|
if (!memcmp(context->output, packet->data, sizeof(arp_packet_t)))
|
||||||
|
context->output_as_expected = true;
|
||||||
|
else {
|
||||||
|
snprintf(context->msg, context->msgmax, "ARP module sent a different packet than expected");
|
||||||
|
context->output_as_expected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
arp_testcase_result_t arp_testcase_run(arp_testcase_t tcase, char *msg, size_t msgmax)
|
||||||
|
{
|
||||||
|
if (tcase.failed)
|
||||||
|
return ARP_TESTCASE_ABORTED;
|
||||||
|
|
||||||
|
arp_packet_t output;
|
||||||
|
|
||||||
|
testcase_contex_t context = {
|
||||||
|
.output = &output,
|
||||||
|
.tcase = &tcase,
|
||||||
|
.cursor = 0,
|
||||||
|
.msg = msg,
|
||||||
|
.msgmax = msgmax,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (msgmax > 0)
|
||||||
|
msg[0] = '\0';
|
||||||
|
|
||||||
|
arp_state_t state;
|
||||||
|
arp_init(&state, tcase.self_ip, tcase.self_mac, &context, send_packet);
|
||||||
|
arp_change_output_buffer(&state, &output, sizeof(output));
|
||||||
|
|
||||||
|
while (context.cursor < tcase.count) {
|
||||||
|
|
||||||
|
arp_testcase_packet_t *packet = tcase.packets + context.cursor++;
|
||||||
|
|
||||||
|
if (packet->sender != ARP_TESTCASE_SENDER_PEER) {
|
||||||
|
snprintf(msg, msgmax, "ARP module didn't send packet");
|
||||||
|
return ARP_TESTCASE_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize these
|
||||||
|
context.output_as_expected = true;
|
||||||
|
context.aborted_while_checking_sent_packets = false;
|
||||||
|
|
||||||
|
arp_process_result_t status = arp_process_packet(&state, packet->data, sizeof(arp_packet_t));
|
||||||
|
|
||||||
|
(void) status; // Not useful yet
|
||||||
|
|
||||||
|
// Before this point the arp_process_packet will have
|
||||||
|
// sent some packets that were validated in the send_packet
|
||||||
|
// callback. If the testcase didn't fail, the next
|
||||||
|
// packet in the list will be sent by the peer.
|
||||||
|
if (context.aborted_while_checking_sent_packets)
|
||||||
|
return ARP_TESTCASE_ABORTED;
|
||||||
|
if (!context.output_as_expected)
|
||||||
|
return ARP_TESTCASE_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
arp_free(&state);
|
||||||
|
return ARP_TESTCASE_PASSED;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#include <stdbool.h>
|
||||||
|
#include "../src/arp.h"
|
||||||
|
|
||||||
|
#define ARP_TESTCASE_MAX_PACKETS 1024
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ARP_TESTCASE_SENDER_HOST,
|
||||||
|
ARP_TESTCASE_SENDER_PEER,
|
||||||
|
} arp_testcase_packet_sender_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
arp_testcase_packet_sender_t sender;
|
||||||
|
const char *data;
|
||||||
|
} arp_testcase_packet_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool failed;
|
||||||
|
|
||||||
|
ip_address_t self_ip;
|
||||||
|
mac_address_t self_mac;
|
||||||
|
|
||||||
|
size_t count;
|
||||||
|
arp_testcase_packet_t packets[ARP_TESTCASE_MAX_PACKETS];
|
||||||
|
} arp_testcase_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ARP_TESTCASE_PASSED,
|
||||||
|
ARP_TESTCASE_FAILED,
|
||||||
|
ARP_TESTCASE_ABORTED,
|
||||||
|
} arp_testcase_result_t;
|
||||||
|
|
||||||
|
void arp_testcase_init(arp_testcase_t *tcase, const char mac[static 6], const char ip[static 4]);
|
||||||
|
void arp_testcase_free(arp_testcase_t *tcase);
|
||||||
|
void arp_testcase_send(arp_testcase_t *tcase, const char data[static sizeof(arp_packet_t)]);
|
||||||
|
void arp_testcase_recv(arp_testcase_t *tcase, const char data[static sizeof(arp_packet_t)]);
|
||||||
|
arp_testcase_result_t arp_testcase_run(arp_testcase_t tcase, char *msg, size_t msgmax);
|
||||||
Reference in New Issue
Block a user