A C implementation of IETF Transport Services (TAPS) with support for QUIC, TCP and UDP.
CTaps provides an asynchronous, callback-based interface for network connections. It supports multistreaming, connection migration (as a proof of concept), session resumption, candidate gathering and candidate racing. Internally it uses Picoquic and libuv.
Transport Services is described in:
CTaps implements several key abstractions from RFC 9622. Among these, the most central to communication are:
| RFC 9622 Concept | CTaps Equivalent | Description |
|---|---|---|
| Preconnection | ct_preconnection_t | Configuration object for setting up Connections before establishment. |
| Connection | ct_connection_t | Active connection created from a Preconnection. |
| Listener | ct_listener_t | Listener for receiving incoming Connections created from a Preconnection. |
| Message | ct_message_t | A single message delivered by one of the underlying protocols. |
| Local Endpoint | ct_local_endpoint_t | A generic or resolved local address to communicate from. |
| Remote Endpoint | ct_remote_endpoint_t | A generic or resolved remote address to communicate with. |
Several other abstractions exist to set up the underlying Connection.
Example CTaps client
#include <arpa/inet.h>
#include <ctaps.h>
#include <stdio.h>
#include <string.h>
void close_on_message_received(ct_connection_t* connection,
ct_message_t* received_message,
ct_message_context_t* message_context) {
uint16_t port = ct_local_endpoint_get_resolved_port(
ct_message_context_get_local_endpoint(message_context)
);
printf("Received message: %s on port %d\n",
ct_message_get_content(received_message), port);
ct_connection_close(connection);
}
void send_message_and_receive(ct_connection_t* connection) {
ct_message_t* message =
ct_message_new_with_content("ping", strlen("ping") + 1);
// CTaps takes a deep copy of the passed content,
// so the message can be freed after this returns
ct_send_message(connection, message);
ct_message_free(message);
ct_receive_callbacks_t receive_message_request = {
.receive_callback = close_on_message_received,
};
ct_receive_message(connection, &receive_message_request);
}
void free_on_connection_closed(ct_connection_t* connection) {
ct_connection_free(connection);
}
int main() {
ct_initialize(); // Init global state
// Create remote endpoint (where we will try to connect to)
ct_remote_endpoint_t* remote_endpoint = ct_remote_endpoint_new();
ct_remote_endpoint_with_ipv4(remote_endpoint, inet_addr("127.0.0.1"));
ct_remote_endpoint_with_port(remote_endpoint, 1234); // example port
// Create transport properties
ct_transport_properties_t* transport_properties =
ct_transport_properties_new();
// selection properties decide which protocol(s) will be used,
// if multiple are compatible with our requirements,
// then we will race the protocols TCP is the only protocol
// compatible with this requirement
ct_transport_properties_set_preserve_msg_boundaries(
transport_properties, PROHIBIT);
// Create preconnection
// No local endpoint, so will bind to wildcard
ct_preconnection_t* preconnection = ct_preconnection_new(
NULL, 0, &remote_endpoint, 1, transport_properties, NULL);
ct_connection_callbacks_t connection_callbacks = {
.ready = send_message_and_receive,
.closed = free_on_connection_closed
};
// Gather potential endpoints and start racing, when event loop starts
int rc = ct_preconnection_initiate(
preconnection,
&connection_callbacks);
if (rc < 0) {
perror("Error in initiating connection\n");
return rc;
}
// Block until all connections close (or no connection can be established)
ct_start_event_loop();
// Cleanup
ct_preconnection_free(preconnection);
ct_transport_properties_free(transport_properties);
ct_remote_endpoint_free(remote_endpoint);
ct_close();
return 0;
}Example CTaps server
#include <ctaps.h>
#include <stdio.h>
#include <string.h>
void close_on_message_received(ct_connection_t* connection,
ct_message_t* received_message,
ct_message_context_t* message_context) {
uint16_t port = ct_local_endpoint_get_resolved_port(
ct_message_context_get_local_endpoint(message_context)
);
printf("Received message: %s on port %d\n",
ct_message_get_content(received_message), port);
ct_connection_close(connection);
}
void on_connection_received_receive_message(ct_listener_t* listener,
ct_connection_t* new_connection) {
printf("Listener received new connection\n");
ct_receive_callbacks_t receive_message_request = {
.receive_callback = close_on_message_received,
};
ct_receive_message(new_connection, &receive_message_request);
// Stop accepting new connections after the first one is received
ct_listener_close(listener);
}
void free_on_listener_closed(ct_listener_t* listener) {
ct_listener_free(listener);
}
void free_on_connection_closed(ct_connection_t* connection) {
ct_connection_free(connection);
}
int main() {
ct_initialize(); // Init logging and event loop
ct_set_log_level(CT_LOG_INFO);
// Create transport properties
ct_transport_properties_t* listener_props = ct_transport_properties_new();
ct_transport_properties_set_preserve_msg_boundaries(
listener_props, PROHIBIT); // Force TCP
ct_local_endpoint_t* local_endpoint = ct_local_endpoint_new();
ct_local_endpoint_with_port(local_endpoint, 1234);
// Create preconnection
ct_preconnection_t* preconnection = ct_preconnection_new(
&local_endpoint, 1, NULL, 0, listener_props, NULL);
ct_listener_callbacks_t listener_callbacks = {
.connection_received = on_connection_received_receive_message,
.listener_closed = free_on_listener_closed
};
ct_connection_callbacks_t connection_callbacks = {
.closed = free_on_connection_closed
};
int rc = ct_preconnection_listen(
preconnection, &listener_callbacks, &connection_callbacks);
if (rc < 0) {
perror("Sync error in establishing listener\n");
return -1;
}
ct_start_event_loop();
// Cleanup
ct_preconnection_free(preconnection);
ct_transport_properties_free(listener_props);
ct_local_endpoint_free(local_endpoint);
ct_close();
return 0;
}Additional examples can be found in the CTaps example project.
ctaps/
├── benchmark/ # Code used to compare CTaps to native benchmarks
├── include/ # Public API headers
│ └── ctaps.h # Public interface
├── examples/ # Example client and server from this README
├── src/ # Implementation
│ ├── connection/ # (pre)connection abstractions
│ ├── protocol/ # Protocol interfaces (TCP, UDP, QUIC) and setup
│ ├── candidate_gathering/ # Protocol/endpoint selection and racing
│ └── ...
└── test/ # Test suite (googletest)
Most dependencies are installed automatically by CMake via FetchContent, but
libglib2.0-dev must be installed separately:
sudo apt-get install libglib2.0-devcmake . -B out/Debug
cmake --build out/Debug --target allNote that the migration tests require CAP_NET_ADMIN
and are by default not built. They can be built by
setting the CTAPS_ENABLE_MIGRATION_TESTS option
in CMake:
cmake . -B out/Debug -DCTAPS_ENABLE_MIGRATION_TESTS=ON
cmake --build out/Debug --target allcd out/Debug/test && ctestSee the CTaps example project for an example on how to fetch CTaps as a dependency using CMake.
CTaps is supported on Linux only.
CTaps was developed as part of a master's thesis at UiO, the final thesis will be added here when published.
This project is licensed under the MIT license.