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 documentation is available on GitHub pages, with the most useful overview being the topics page
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,
// TCP is preferred with this requirement
ct_transport_properties_set_preserve_msg_boundaries(
transport_properties, AVOID);
const ct_remote_endpoint_t* remotes[] = {remote_endpoint};
// Create preconnection
// No local endpoint, so will bind to wildcard
ct_preconnection_t* preconnection = ct_preconnection_new(
NULL, 0, remotes, 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_multistreaming(
listener_props, AVOID); // Prefer TCP
ct_local_endpoint_t* local_endpoint = ct_local_endpoint_new();
ct_local_endpoint_with_port(local_endpoint, 1234);
const ct_local_endpoint_t* locals[] = {local_endpoint};
// Create preconnection
ct_preconnection_t* preconnection = ct_preconnection_new(
locals, 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.
Some system-level dependencies must be installed separately:
sudo apt-get install pkg-config libglib2.0-dev libssl-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.