diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..4b6b03e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "deps/tquic"] + path = deps/tquic + url = https://github.com/Tencent/tquic + ignore = dirty diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e5201fa --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +TQUIC_DIR = deps/tquic +LIB_DIR = $(TQUIC_DIR)/target/release +INCLUDE_DIR = $(TQUIC_DIR)/include + +INCS = -I$(INCLUDE_DIR) +CFLAGS = -I. -Wall -Werror -pedantic -fsanitize=address -g -static-libasan -I$(TQUIC_DIR)/deps/boringssl/src/include/ + +LDFLAGS = -L$(LIB_DIR) + +LIBS = $(LIB_DIR)/libtquic.a -lev -ldl -lm + +all: simple_server + +simple_server: simple_server.c $(LIB_DIR)/libtquic.a + $(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ $(INCS) $(LIBS) + +$(LIB_DIR)/libtquic.a: + git submodule update --init --recursive && cd $(TQUIC_DIR) && cargo build --release -F ffi + +clean: + @$(RM) -rf simple_server diff --git a/README.md b/README.md index 215862e..9f58765 100644 --- a/README.md +++ b/README.md @@ -1 +1,27 @@ -# tquic-example-c \ No newline at end of file +# tquic-example-c + +C examples of using [TQUIC](https://github.com/Tencent/tquic) on Linux. + +**simple_server** + +A simple http/0.9 server responsing "OK" to any requests. + +The certificate and private key are hard coded to "cert.crt" and "cert.key". + +The first argument is the listening IP and the second is the listening port. + +## Requirements + +Refer to the [TQUIC](https://tquic.net/docs/getting_started/installation#prerequisites) prerequisites. + +## Build + +```shell +make +``` + +## Run simple_server + +```shell +./simple_server 0.0.0.0 4433 +``` \ No newline at end of file diff --git a/cert.crt b/cert.crt new file mode 100644 index 0000000..e303751 --- /dev/null +++ b/cert.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDEjCCAfqgAwIBAgIJAIAdu56fLE7OMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNV +BAMMC2V4YW1wbGUub3JnMCAXDTIwMDYxMDEwMzM0NFoYDzIxMjAwNTE3MTAzMzQ0 +WjAWMRQwEgYDVQQDDAtleGFtcGxlLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBALv+LV1aWIlcK9rI7IuRS8SCusqBnoyJec/ErKiA2gbfgZ/YS73L +Zud84yp45AIqauzcI5q+hrkmsRZ7CKqDzrG+jHavW7jF+0laetJwRt26AcQcOtQD +2ik2O+Dl1WHAFn4vUAQxb+Xz6WfSaQN0QfM74z06XUDDsr7g7+NYtMzhf98SJSoK +ne/dVKJ3Bc6e6tvhnCRwPtix4ektEodK6WeNHYxwJ6wSZ8cRLzdxgjdD/4OGfFuj +dn8zbOi3SQt5ZqVbcDHUTzp5t0G8EoxnzotHhhzjSAmsypySqZXaxl3oX8aYUkFn +fCdg+WBXo5pOiNfoWh/D5bnIXWGp52yoy+kCAwEAAaNhMF8wHQYDVR0lBBYwFAYI +KwYBBQUHAwIGCCsGAQUFBwMBMB8GA1UdIwQYMBaAFIH+0G3eCswQHbN06kvI80M3 +tNH9MB0GA1UdDgQWBBSxLHQE7gOEyfeSNc5uIO/G/rgjpzANBgkqhkiG9w0BAQsF +AAOCAQEAlGm5RwQ79xmLh3rj+5UViCSgsIuMcuhgIT4zogpo9S4uwXMqinrJhzRk +Oc2tb3y06XTAq1lMH2+58tqndAu8ni/UBz3OSghk2CTnZ1vxxXOd3CtQu4ypMq+k +qW0Umdrkk5TeAODNbrCy4c6vpICkQOljnRFWnDYu3aQ3JvaWZ/nObN7C72Lgpjfb +RfLXGmLsBCEIr028f9hpoeRCXoetUY2CiC2boAHR+cO6Jpvex4Jv5yYDpNKac52n +LLC8Cq5ozhOZNOSV6X9FpEca3rdhVUb0VgoNDCPZDdpO1PDJYCN9fUC8KUs+dsOh +SYGpliBaNsztoiJs1q5SkMQrMwmMmg== +-----END CERTIFICATE----- + diff --git a/cert.key b/cert.key new file mode 100644 index 0000000..a169fcf --- /dev/null +++ b/cert.key @@ -0,0 +1,28 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAu/4tXVpYiVwr2sjsi5FLxIK6yoGejIl5z8SsqIDaBt+Bn9hL +vctm53zjKnjkAipq7Nwjmr6GuSaxFnsIqoPOsb6Mdq9buMX7SVp60nBG3boBxBw6 +1APaKTY74OXVYcAWfi9QBDFv5fPpZ9JpA3RB8zvjPTpdQMOyvuDv41i0zOF/3xIl +Kgqd791UoncFzp7q2+GcJHA+2LHh6S0Sh0rpZ40djHAnrBJnxxEvN3GCN0P/g4Z8 +W6N2fzNs6LdJC3lmpVtwMdRPOnm3QbwSjGfOi0eGHONICazKnJKpldrGXehfxphS +QWd8J2D5YFejmk6I1+haH8PluchdYannbKjL6QIDAQABAoIBAEUBL7WsjAMfihls +1ycD1kPzmIzstz3u2H+jOZ1AbsdHE1WRF3w7RTKDbP8SEN+aolT/GTKb7OfZg/c0 +giHU7/Hed8C47XoNcgei5qKIA/svY6aQlidsoo+uEJykwIZ488itpTlkzCYkOfCa +E2HpMqwNt4OqAMDdFKdr+aIB1Zu+KPBxW23WD9wEWAbe5LA4YnRF9kT4YZ6y9mce +dGaIf39VtBlrGMmvoU0LE9B79nyuebGi0svW6QDarBqaDrnM/N3fXgL1kk/gVfan +/xs6EA4qPxA5G4h+enYrIlZL0CbSj60nYElo+Z5nRdBaRdCF/bpXOLyK/kXWLUM0 +f2HTK+ECgYEA5cVcpJtczxEaxoaEUbppsW1LCrTjJDGTKJ63G7/lwqCxJeCHN185 +nnckHOW2287e19bu9aUmKJgRq5s1rXnT+MnCl/hQnfaMrKOKtzE+t7/zsc9+LuAr +pwJrtZ9Dcnwrk8NOE0fPjW5XpDCSoEOo7JWZmGTVlpabOgNkjocfp+sCgYEA0XPt +ZPt3F0wyzgLYRhgnvp5CV8SzQmulsW+ytnL5eiAcNSXqni3wQHN3PGxLInEyQwBQ +/M8TQpUbqGMmahCK4ZxMAwXMrpF0mVB8jfoYMou1FSYPlUV+CvLjWcTkZB1Nirez +VFXdtfHP0mx4PbYK2qjB03u4pPHAN8kuayIf2nsCgYAbv/FHZAgabfto3Jggcr4P +Ep8MhPolxeL69egxbsSl89hRNcO+2T5ROBxhbRDfjSV2tduYSUDJiEwiCJW8BMmn +814QEopR+ZPVyc6X/1eOw5z/7YpUyPgcrHsrrTdtHTf6GY1VYMfdUeU9zCv5NRKy +uAKb2Bm/nSLUJ9K+L+2PzwKBgQDRQgT3UtTUjehkMitpPFDY/LxDe92simfsMjBW +X+Anx1TnNI6GolbZzYJe98LJElao4fQH38raRqZvQT/rz8MxTDoU+wJXljLryaHn +Jupt9W5hRrli5R7cSXYjBbc43p3N7WJY68CqOoDrNjubS/jkJJ4hcAY1pOHp2jFq +D5nLaQKBgAysU6O5kJ8yKxhbZflb42MqKCFBGrbRnbYx14PAEZOaRhzxehpppQmx +RLbn/z1Uh5Ms28ipxA+vnhyM3FcU5lKboaFyWJeuNslw0FxEcIai6hL6UkDznS4G +aqyzUjpG5Chg0x18xWYCbiGJwjZ9BWhtH+jojm856QHGzeQJWVoo +-----END RSA PRIVATE KEY----- + diff --git a/deps/tquic b/deps/tquic new file mode 160000 index 0000000..d8cf4ab --- /dev/null +++ b/deps/tquic @@ -0,0 +1 @@ +Subproject commit d8cf4ab44ca57d4989840df3e0813f47962ab363 diff --git a/simple_server.c b/simple_server.c new file mode 100644 index 0000000..1981958 --- /dev/null +++ b/simple_server.c @@ -0,0 +1,379 @@ +// Copyright (c) 2023 The TQUIC Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define READ_BUF_SIZE 4096 +#define MAX_DATAGRAM_SIZE 1200 + +// Simple QUIC http/0.9 server, return same response to any requests. +struct simple_server { + struct quic_endpoint_t *quic_endpoint; + ev_timer timer; + int sock; + struct sockaddr_storage local_addr; + socklen_t local_addr_len; + SSL_CTX *ssl_ctx; +}; + +void server_on_conn_created(void *tctx, struct quic_conn_t *conn) +{ + fprintf(stderr, "new connection created\n"); +} + +void server_on_conn_established(void *tctx, struct quic_conn_t *conn) +{ + fprintf(stderr, "connection established\n"); +} + +void server_on_conn_closed(void *tctx, struct quic_conn_t *conn) +{ + fprintf(stderr, "connection closed\n"); +} + +void server_on_stream_created(void *tctx, struct quic_conn_t *conn, + uint64_t stream_id) +{ + fprintf(stderr, "new stream created %ld\n", stream_id); +} + +void server_on_stream_readable(void *tctx, struct quic_conn_t *conn, + uint64_t stream_id) +{ + static uint8_t buf[READ_BUF_SIZE]; + bool fin = false; + ssize_t r = quic_stream_read(conn, stream_id, buf, READ_BUF_SIZE, &fin); + if (r < 0) { + fprintf(stderr, "stream[%ld] read error\n", stream_id); + return; + } + + printf("Got request:\n"); + printf("%.*s\n", (int) r, buf); + + if (fin) { + static const char *resp = "OK"; + quic_stream_write(conn, stream_id, (uint8_t *) resp, 2, true); + } +} + +void server_on_stream_writable(void *tctx, struct quic_conn_t *conn, + uint64_t stream_id) +{} + +void server_on_stream_closed(void *tctx, struct quic_conn_t *conn, + uint64_t stream_id) +{ + fprintf(stderr, "new stream closed %ld\n", stream_id); +} + +int server_on_packets_send(void *psctx, struct quic_packet_out_spec_t *pkts, + unsigned int count) +{ + struct simple_server *server = psctx; + + unsigned int sent_count = 0; + int i, j = 0; + for (i = 0; i < count; i++) { + struct quic_packet_out_spec_t *pkt = pkts + i; + for (j = 0; j < (*pkt).iovlen; j++) { + const struct iovec *iov = pkt->iov + j; + ssize_t sent = sendto(server->sock, iov->iov_base, iov->iov_len, 0, + (struct sockaddr *) pkt->dst_addr, + pkt->dst_addr_len); + + if (sent != iov->iov_len) { + if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) { + fprintf(stderr, "send would block, already sent: %d\n", + sent_count); + return sent_count; + } + return -1; + } + fprintf(stderr, "send packet, length %ld\n", sent); + sent_count++; + } + } + + return sent_count; +} + +const SSL_CTX *server_get_default_tls_config(void *ctx) +{ + struct simple_server *server = ctx; + return server->ssl_ctx; +} + +const SSL_CTX *server_select_tls_config(void *ctx, + const uint8_t *server_name, + size_t server_name_len) +{ + struct simple_server *server = ctx; + return server->ssl_ctx; +} + +static char s_alpn[0x100]; + +static int add_alpn (const char *alpn) +{ + size_t alpn_len, all_len; + + alpn_len = strlen(alpn); + if (alpn_len > 255) + return -1; + + all_len = strlen(s_alpn); + if (all_len + 1 + alpn_len + 1 > sizeof(s_alpn)) + return -1; + + s_alpn[all_len] = alpn_len; + memcpy(&s_alpn[all_len + 1], alpn, alpn_len); + s_alpn[all_len + 1 + alpn_len] = '\0'; + return 0; +} + +static int select_alpn (SSL *ssl, const unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) +{ + int r = SSL_select_next_proto((unsigned char **) out, outlen, in, inlen, + (unsigned char *) s_alpn, strlen(s_alpn)); + if (r == OPENSSL_NPN_NEGOTIATED) + return SSL_TLSEXT_ERR_OK; + else + { + return SSL_TLSEXT_ERR_ALERT_FATAL; + } +} + +int server_load_ssl_ctx(struct simple_server *server) +{ + add_alpn("http/0.9"); + server->ssl_ctx = SSL_CTX_new(TLS_method()); + if (SSL_CTX_use_certificate_chain_file(server->ssl_ctx, "./cert.crt") != 1) { + fprintf(stderr, "failed to load cert\n"); + return -1; + } + if (SSL_CTX_use_PrivateKey_file(server->ssl_ctx, "./cert.key", SSL_FILETYPE_PEM) != 1) { + fprintf(stderr, "failed to load key\n"); + return -1; + } + SSL_CTX_set_default_verify_paths(server->ssl_ctx); + SSL_CTX_set_alpn_select_cb(server->ssl_ctx, select_alpn, NULL); + + return 0; +} + +const struct quic_transport_methods_t quic_transport_methods = { + .on_conn_created = server_on_conn_created, + .on_conn_established = server_on_conn_established, + .on_conn_closed = server_on_conn_closed, + .on_stream_created = server_on_stream_created, + .on_stream_readable = server_on_stream_readable, + .on_stream_writable = server_on_stream_writable, + .on_stream_closed = server_on_stream_closed, +}; + +const struct quic_packet_send_methods_t quic_packet_send_methods = { + .on_packets_send = server_on_packets_send, +}; + +const struct quic_tls_config_select_methods_t tls_config_select_method = { + .get_default = server_get_default_tls_config, + .select = server_select_tls_config, +}; + +static void read_callback(EV_P_ ev_io *w, int revents) +{ + struct simple_server *server = w->data; + static uint8_t buf[READ_BUF_SIZE]; + + while (true) { + struct sockaddr_storage peer_addr; + socklen_t peer_addr_len = sizeof(peer_addr); + memset(&peer_addr, 0, peer_addr_len); + + ssize_t read = recvfrom(server->sock, buf, sizeof(buf), 0, + (struct sockaddr *) &peer_addr, + &peer_addr_len); + if (read < 0) { + if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) { + fprintf(stderr, "recv would block\n"); + break; + } + + fprintf(stderr, "failed to read\n"); + return; + } + + quic_packet_info_t quic_packet_info = { + .src = (struct sockaddr *) &peer_addr, + .src_len = peer_addr_len, + .dst = (struct sockaddr *) &server->local_addr, + .dst_len = server->local_addr_len, + }; + + int r = quic_endpoint_recv(server->quic_endpoint, buf, read, + &quic_packet_info); + if (r != 0) { + fprintf(stderr, "recv failed %d\n", r); + continue; + } + } + + quic_endpoint_process_connections(server->quic_endpoint); + double timeout = quic_endpoint_timeout(server->quic_endpoint) /1e3f; + server->timer.repeat = timeout; + ev_timer_again(loop, &server->timer); +} + +static void timeout_callback(EV_P_ ev_timer *w, int revents) +{ + struct simple_server *server = w->data; + quic_endpoint_on_timeout(server->quic_endpoint); + quic_endpoint_process_connections(server->quic_endpoint); + + double timeout = quic_endpoint_timeout(server->quic_endpoint) /1e3f; + server->timer.repeat = timeout; + ev_timer_again(loop, &server->timer); +} + +static void debug_log(const unsigned char *line, void *argp) +{ + fprintf(stderr, "%s\n", line); +} + +int main(int argc, char *argv[]) +{ + // TODO: add more arguments and command line parsing. + const char *host = argv[1]; + const char *port = argv[2]; + + // Set logger. + quic_set_logger(debug_log, NULL, "TRACE"); + + // Bind socket. + const struct addrinfo hints = { + .ai_family = PF_UNSPEC, + .ai_socktype = SOCK_DGRAM, + .ai_protocol = IPPROTO_UDP + }; + + struct addrinfo *local; + if (getaddrinfo(host, port, &hints, &local) != 0) { + fprintf(stderr, "failed to resolve host\n"); + freeaddrinfo(local); + return -1; + } + + int sock = socket(local->ai_family, SOCK_DGRAM, 0); + if (sock < 0) { + fprintf(stderr, "failed to create socket\n"); + freeaddrinfo(local); + return -1; + } + + if (fcntl(sock, F_SETFL, O_NONBLOCK) != 0) { + fprintf(stderr, "failed to make socket non-blocking\n"); + freeaddrinfo(local); + return -1; + } + + if (bind(sock, local->ai_addr, local->ai_addrlen) < 0) { + fprintf(stderr, "failed to bind socket\n"); + freeaddrinfo(local); + return -1; + } + + // Create quic config. + quic_config_t *config = quic_config_new(); + if (config == NULL) { + fprintf(stderr, "failed to create config\n"); + return -1; + } + quic_config_set_max_idle_timeout(config, 5000); + quic_config_set_recv_udp_payload_size(config, MAX_DATAGRAM_SIZE); + + // Create simple server. + struct simple_server server; + server.sock = sock; + server.local_addr_len = sizeof(server.local_addr); + if (getsockname(sock, (struct sockaddr *)&server.local_addr, + &server.local_addr_len) != 0) + { + fprintf(stderr, "failed to get local address of socket\n"); + return -1; + }; + + quic_transport_handler_t quic_transport_handler = { + .methods = &quic_transport_methods, + .context = &server, + }; + + quic_packet_send_handler_t quic_packet_send_handler = { + .methods = &quic_packet_send_methods, + .context = &server, + }; + + struct quic_endpoint_t *quic_endpoint = + quic_endpoint_new(config, true, &quic_transport_handler, + &quic_packet_send_handler); + if (quic_endpoint == NULL) { + fprintf(stderr, "failed to create quic endpoint\n"); + return -1; + } + server.quic_endpoint = quic_endpoint; + + // Create and set tls conf selector for quic config. + if (server_load_ssl_ctx(&server) != 0) { + return -1; + } + quic_config_set_tls_selector(config, &tls_config_select_method, &server); + + // Start event loop. + struct ev_loop *loop = ev_default_loop(0); + ev_init(&server.timer, timeout_callback); + server.timer.data = &server; + + ev_io watcher; + ev_io_init(&watcher, read_callback, sock, EV_READ); + ev_io_start(loop, &watcher); + watcher.data = &server; + ev_loop(loop, 0); + + // Event loop end. + freeaddrinfo(local); + SSL_CTX_free(server.ssl_ctx); + // TODO: solve memory release problem. + quic_endpoint_free(quic_endpoint); + + return 0; +}