From 0bd298c0db64cf936a99b352239b9875addaf067 Mon Sep 17 00:00:00 2001 From: Jordi Cenzano Ferret Date: Wed, 30 Jun 2021 15:23:42 +0200 Subject: [PATCH 01/10] Added generated files to gitignore --- .gitignore | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.gitignore b/.gitignore index 650d91dc6..6e3f044ed 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,12 @@ cmake_install.cmake xcuserdata *.xccheckout tmp/ +cli +embedded-probes.h +examples-echo +libquicly.a +picotls-probes.h +quicly-probes.h +quicly-tracer.h +test.t +udpfw \ No newline at end of file From 3569a1914b5beed9e8f39ddf69b4fd2a740378de Mon Sep 17 00:00:00 2001 From: Jordi Cenzano Ferret Date: Wed, 30 Jun 2021 15:25:20 +0200 Subject: [PATCH 02/10] Added white space end gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6e3f044ed..ac0fbb831 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,4 @@ picotls-probes.h quicly-probes.h quicly-tracer.h test.t -udpfw \ No newline at end of file +udpfw From 85996fc42f851d20985e5b1b9a90324f41ca5c15 Mon Sep 17 00:00:00 2001 From: Jordi Cenzano Ferret Date: Wed, 30 Jun 2021 16:08:05 +0200 Subject: [PATCH 03/10] Fixes deprecation warning for cmake_minimum_required --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a2ed00ec0..72da347c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.8.11) +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.11...3.20) CMAKE_POLICY(SET CMP0003 NEW) PROJECT(quicly) From 88f526f258eb84fe9924435ab9581f95cd06776d Mon Sep 17 00:00:00 2001 From: Jordi Cenzano Ferret Date: Wed, 30 Jun 2021 16:27:41 +0200 Subject: [PATCH 04/10] Added pipe source files (copy as echo) and included them in Cmake --- .vscode/launch.json | 54 ++++++ CMakeLists.txt | 6 + examples/pipeclient.c | 399 ++++++++++++++++++++++++++++++++++++++++++ examples/pipeserver.c | 399 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 858 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 examples/pipeclient.c create mode 100644 examples/pipeserver.c diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..2715e72e6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,54 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "example-pipe-server", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceRoot}/build/examples-pipeserver", + "args": ["-h"], + "stopAtEntry": false, + "cwd": "${fileDirname}", + "environment": [], + "externalConsole": false, + "MIMode": "lldb" + },{ + "name": "example-pipe-client", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceRoot}/build/examples-pipeclient", + "args": ["-h"], + "stopAtEntry": false, + "cwd": "${fileDirname}", + "environment": [], + "externalConsole": false, + "MIMode": "lldb" + },{ + "name": "example-echo", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceRoot}/build/examples-echo", + "args": ["-h"], + "stopAtEntry": false, + "cwd": "${fileDirname}", + "environment": [], + "externalConsole": false, + "MIMode": "lldb" + }, + { + "name": "cli", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceRoot}/build/cli", + "args": ["-h"], + "stopAtEntry": false, + "cwd": "${fileDirname}", + "environment": [], + "externalConsole": false, + "MIMode": "lldb" + } + ] +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 72da347c9..58907ae48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,6 +124,12 @@ TARGET_LINK_LIBRARIES(test.t quicly ${OPENSSL_LIBRARIES} ${CMAKE_DL_LIBS}) ADD_EXECUTABLE(examples-echo ${PICOTLS_OPENSSL_FILES} examples/echo.c) TARGET_LINK_LIBRARIES(examples-echo quicly ${OPENSSL_LIBRARIES} ${CMAKE_DL_LIBS}) +ADD_EXECUTABLE(examples-pipeclient ${PICOTLS_OPENSSL_FILES} examples/pipeclient.c) +TARGET_LINK_LIBRARIES(examples-pipeclient quicly ${OPENSSL_LIBRARIES} ${CMAKE_DL_LIBS}) + +ADD_EXECUTABLE(examples-pipeserver ${PICOTLS_OPENSSL_FILES} examples/pipeserver.c) +TARGET_LINK_LIBRARIES(examples-pipeserver quicly ${OPENSSL_LIBRARIES} ${CMAKE_DL_LIBS}) + ADD_EXECUTABLE(udpfw t/udpfw.c) ADD_CUSTOM_TARGET(check env BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR} WITH_DTRACE=${WITH_DTRACE} prove --exec "sh -c" -v ${CMAKE_CURRENT_BINARY_DIR}/*.t t/*.t diff --git a/examples/pipeclient.c b/examples/pipeclient.c new file mode 100644 index 000000000..a6723a0a1 --- /dev/null +++ b/examples/pipeclient.c @@ -0,0 +1,399 @@ +/* + * Copyright (c) 2019 Fastly, Kazuho Oku + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 700 /* required for glibc to use getaddrinfo, etc. */ +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "picotls.h" +#include "picotls/openssl.h" +#include "quicly.h" +#include "quicly/defaults.h" +#include "quicly/streambuf.h" + +/** + * the QUIC context + */ +static quicly_context_t ctx; +/** + * CID seed + */ +static quicly_cid_plaintext_t next_cid; + +static int resolve_address(struct sockaddr *sa, socklen_t *salen, const char *host, const char *port, int family, int type, + int proto) +{ + struct addrinfo hints, *res; + int err; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = type; + hints.ai_protocol = proto; + hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV | AI_PASSIVE; + if ((err = getaddrinfo(host, port, &hints, &res)) != 0 || res == NULL) { + fprintf(stderr, "failed to resolve address:%s:%s:%s\n", host, port, + err != 0 ? gai_strerror(err) : "getaddrinfo returned NULL"); + return -1; + } + + memcpy(sa, res->ai_addr, res->ai_addrlen); + *salen = res->ai_addrlen; + + freeaddrinfo(res); + return 0; +} + +static void usage(const char *progname) +{ + printf("Usage: %s [options] [host]\n" + "Options:\n" + " -c specifies the certificate chain file (PEM format)\n" + " -k specifies the private key file (PEM format)\n" + " -p specifies the port number (default: 4433)\n" + " -h prints this help\n" + "\n" + "When both `-c` and `-k` is specified, runs as a server. Otherwise, runs as a\n" + "client connecting to host:port. If omitted, host defaults to 127.0.0.1.\n", + progname); + exit(0); +} + +static int is_server(void) +{ + return ctx.tls->certificates.count != 0; +} + +static int forward_stdin(quicly_conn_t *conn) +{ + quicly_stream_t *stream0; + char buf[4096]; + size_t rret; + + if ((stream0 = quicly_get_stream(conn, 0)) == NULL || !quicly_sendstate_is_open(&stream0->sendstate)) + return 0; + + while ((rret = read(0, buf, sizeof(buf))) == -1 && errno == EINTR) + ; + if (rret == 0) { + /* stdin closed, close the send-side of stream0 */ + quicly_streambuf_egress_shutdown(stream0); + return 0; + } else { + /* write data to send buffer */ + quicly_streambuf_egress_write(stream0, buf, rret); + return 1; + } +} + +static void on_stop_sending(quicly_stream_t *stream, int err) +{ + fprintf(stderr, "received STOP_SENDING: %" PRIu16 "\n", QUICLY_ERROR_GET_ERROR_CODE(err)); + quicly_close(stream->conn, QUICLY_ERROR_FROM_APPLICATION_ERROR_CODE(0), ""); +} + +static void on_receive_reset(quicly_stream_t *stream, int err) +{ + fprintf(stderr, "received RESET_STREAM: %" PRIu16 "\n", QUICLY_ERROR_GET_ERROR_CODE(err)); + quicly_close(stream->conn, QUICLY_ERROR_FROM_APPLICATION_ERROR_CODE(0), ""); +} + +static void on_receive(quicly_stream_t *stream, size_t off, const void *src, size_t len) +{ + /* read input to receive buffer */ + if (quicly_streambuf_ingress_receive(stream, off, src, len) != 0) + return; + + /* obtain contiguous bytes from the receive buffer */ + ptls_iovec_t input = quicly_streambuf_ingress_get(stream); + + if (is_server()) { + /* server: echo back to the client */ + if (quicly_sendstate_is_open(&stream->sendstate) && (input.len > 0)) { + quicly_streambuf_egress_write(stream, input.base, input.len); + /* shutdown the stream after echoing all data */ + if (quicly_recvstate_transfer_complete(&stream->recvstate)) + quicly_streambuf_egress_shutdown(stream); + } + } else { + /* client: print to stdout */ + fwrite(input.base, 1, input.len, stdout); + fflush(stdout); + /* initiate connection close after receiving all data */ + if (quicly_recvstate_transfer_complete(&stream->recvstate)) + quicly_close(stream->conn, 0, ""); + } + + /* remove used bytes from receive buffer */ + quicly_streambuf_ingress_shift(stream, input.len); +} + +static void process_msg(int is_client, quicly_conn_t **conns, struct msghdr *msg, size_t dgram_len) +{ + size_t off = 0, i; + + /* split UDP datagram into multiple QUIC packets */ + while (off < dgram_len) { + quicly_decoded_packet_t decoded; + if (quicly_decode_packet(&ctx, &decoded, msg->msg_iov[0].iov_base, dgram_len, &off) == SIZE_MAX) + return; + /* find the corresponding connection (TODO handle version negotiation, rebinding, retry, etc.) */ + for (i = 0; conns[i] != NULL; ++i) + if (quicly_is_destination(conns[i], NULL, msg->msg_name, &decoded)) + break; + if (conns[i] != NULL) { + /* let the current connection handle ingress packets */ + quicly_receive(conns[i], NULL, msg->msg_name, &decoded); + } else if (!is_client) { + /* assume that the packet is a new connection */ + quicly_accept(conns + i, &ctx, NULL, msg->msg_name, &decoded, NULL, &next_cid, NULL); + } + } +} + +static int send_one(int fd, struct sockaddr *dest, struct iovec *vec) +{ + struct msghdr mess = {.msg_name = dest, .msg_namelen = quicly_get_socklen(dest), .msg_iov = vec, .msg_iovlen = 1}; + int ret; + + while ((ret = (int)sendmsg(fd, &mess, 0)) == -1 && errno == EINTR) + ; + return ret; +} + +static int run_loop(int fd, quicly_conn_t *client) +{ + quicly_conn_t *conns[256] = {client}; /* a null-terminated list of connections; proper app should use a hashmap or something */ + size_t i; + int read_stdin = client != NULL; + + while (1) { + + /* wait for sockets to become readable, or some event in the QUIC stack to fire */ + fd_set readfds; + struct timeval tv; + do { + int64_t first_timeout = INT64_MAX, now = ctx.now->cb(ctx.now); + for (i = 0; conns[i] != NULL; ++i) { + int64_t conn_timeout = quicly_get_first_timeout(conns[i]); + if (conn_timeout < first_timeout) + first_timeout = conn_timeout; + } + if (now < first_timeout) { + int64_t delta = first_timeout - now; + if (delta > 1000 * 1000) + delta = 1000 * 1000; + tv.tv_sec = delta / 1000; + tv.tv_usec = (delta % 1000) * 1000; + } else { + tv.tv_sec = 1000; + tv.tv_usec = 0; + } + FD_ZERO(&readfds); + FD_SET(fd, &readfds); + /* we want to read input from stdin */ + if (read_stdin) + FD_SET(0, &readfds); + } while (select(fd + 1, &readfds, NULL, NULL, &tv) == -1 && errno == EINTR); + + /* read the QUIC fd */ + if (FD_ISSET(fd, &readfds)) { + uint8_t buf[4096]; + struct sockaddr_storage sa; + struct iovec vec = {.iov_base = buf, .iov_len = sizeof(buf)}; + struct msghdr msg = {.msg_name = &sa, .msg_namelen = sizeof(sa), .msg_iov = &vec, .msg_iovlen = 1}; + ssize_t rret; + while ((rret = recvmsg(fd, &msg, 0)) == -1 && errno == EINTR) + ; + if (rret > 0) + process_msg(client != NULL, conns, &msg, rret); + } + + /* read stdin, send the input to the active stram */ + if (FD_ISSET(0, &readfds)) { + assert(client != NULL); + if (!forward_stdin(client)) + read_stdin = 0; + } + + /* send QUIC packets, if any */ + for (i = 0; conns[i] != NULL; ++i) { + quicly_address_t dest, src; + struct iovec dgrams[10]; + uint8_t dgrams_buf[PTLS_ELEMENTSOF(dgrams) * ctx.transport_params.max_udp_payload_size]; + size_t num_dgrams = PTLS_ELEMENTSOF(dgrams); + int ret = quicly_send(conns[i], &dest, &src, dgrams, &num_dgrams, dgrams_buf, sizeof(dgrams_buf)); + switch (ret) { + case 0: { + size_t j; + for (j = 0; j != num_dgrams; ++j) { + send_one(fd, &dest.sa, &dgrams[j]); + } + } break; + case QUICLY_ERROR_FREE_CONNECTION: + /* connection has been closed, free, and exit when running as a client */ + quicly_free(conns[i]); + memmove(conns + i, conns + i + 1, sizeof(conns) - sizeof(conns[0]) * (i + 1)); + --i; + if (!is_server()) + return 0; + break; + default: + fprintf(stderr, "quicly_send returned %d\n", ret); + return 1; + } + } + } + + return 0; +} + +static int on_stream_open(quicly_stream_open_t *self, quicly_stream_t *stream) +{ + static const quicly_stream_callbacks_t stream_callbacks = { + quicly_streambuf_destroy, quicly_streambuf_egress_shift, quicly_streambuf_egress_emit, on_stop_sending, on_receive, + on_receive_reset}; + int ret; + + if ((ret = quicly_streambuf_create(stream, sizeof(quicly_streambuf_t))) != 0) + return ret; + stream->callbacks = &stream_callbacks; + return 0; +} + +int main(int argc, char **argv) +{ + ptls_openssl_sign_certificate_t sign_certificate; + ptls_context_t tlsctx = { + .random_bytes = ptls_openssl_random_bytes, + .get_time = &ptls_get_time, + .key_exchanges = ptls_openssl_key_exchanges, + .cipher_suites = ptls_openssl_cipher_suites, + }; + quicly_stream_open_t stream_open = {on_stream_open}; + char *host = "127.0.0.1", *port = "4433"; + struct sockaddr_storage sa; + socklen_t salen; + int ch, fd; + + /* setup quic context */ + ctx = quicly_spec_context; + ctx.tls = &tlsctx; + quicly_amend_ptls_context(ctx.tls); + ctx.stream_open = &stream_open; + + /* resolve command line options and arguments */ + while ((ch = getopt(argc, argv, "c:k:p:h")) != -1) { + switch (ch) { + case 'c': /* load certificate chain */ { + int ret; + if ((ret = ptls_load_certificates(&tlsctx, optarg)) != 0) { + fprintf(stderr, "failed to load certificates from file %s:%d\n", optarg, ret); + exit(1); + } + } break; + case 'k': /* load private key */ { + FILE *fp; + if ((fp = fopen(optarg, "r")) == NULL) { + fprintf(stderr, "failed to open file:%s:%s\n", optarg, strerror(errno)); + exit(1); + } + EVP_PKEY *pkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL); + fclose(fp); + if (pkey == NULL) { + fprintf(stderr, "failed to load private key from file:%s\n", optarg); + exit(1); + } + ptls_openssl_init_sign_certificate(&sign_certificate, pkey); + EVP_PKEY_free(pkey); + tlsctx.sign_certificate = &sign_certificate.super; + } break; + case 'p': /* port */ + port = optarg; + break; + case 'h': /* help */ + usage(argv[0]); + break; + default: + exit(1); + break; + } + } + if ((tlsctx.certificates.count != 0) != (tlsctx.sign_certificate != NULL)) { + fprintf(stderr, "-c and -k options must be used together\n"); + exit(1); + } + argc -= optind; + argv += optind; + if (argc != 0) + host = *argv++; + if (resolve_address((struct sockaddr *)&sa, &salen, host, port, AF_INET, SOCK_DGRAM, 0) != 0) + exit(1); + + /* open socket, on the specified port (as a server), or on any port (as a client) */ + if ((fd = socket(sa.ss_family, SOCK_DGRAM, 0)) == -1) { + perror("socket(2) failed"); + exit(1); + } + // fcntl(fd, F_SETFL, O_NONBLOCK); + if (is_server()) { + int reuseaddr = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)); + if (bind(fd, (struct sockaddr *)&sa, salen) != 0) { + perror("bind(2) failed"); + exit(1); + } + } else { + struct sockaddr_in local; + memset(&local, 0, sizeof(local)); + if (bind(fd, (struct sockaddr *)&local, sizeof(local)) != 0) { + perror("bind(2) failed"); + exit(1); + } + } + + quicly_conn_t *client = NULL; + if (!is_server()) { + /* initiate a connection, and open a stream */ + int ret; + if ((ret = quicly_connect(&client, &ctx, host, (struct sockaddr *)&sa, NULL, &next_cid, ptls_iovec_init(NULL, 0), NULL, + NULL)) != 0) { + fprintf(stderr, "quicly_connect failed:%d\n", ret); + exit(1); + } + quicly_stream_t *stream; /* we retain the opened stream via the on_stream_open callback */ + quicly_open_stream(client, &stream, 0); + } + + /* enter the event loop with a connection object */ + return run_loop(fd, client); +} diff --git a/examples/pipeserver.c b/examples/pipeserver.c new file mode 100644 index 000000000..a6723a0a1 --- /dev/null +++ b/examples/pipeserver.c @@ -0,0 +1,399 @@ +/* + * Copyright (c) 2019 Fastly, Kazuho Oku + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 700 /* required for glibc to use getaddrinfo, etc. */ +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "picotls.h" +#include "picotls/openssl.h" +#include "quicly.h" +#include "quicly/defaults.h" +#include "quicly/streambuf.h" + +/** + * the QUIC context + */ +static quicly_context_t ctx; +/** + * CID seed + */ +static quicly_cid_plaintext_t next_cid; + +static int resolve_address(struct sockaddr *sa, socklen_t *salen, const char *host, const char *port, int family, int type, + int proto) +{ + struct addrinfo hints, *res; + int err; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = type; + hints.ai_protocol = proto; + hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV | AI_PASSIVE; + if ((err = getaddrinfo(host, port, &hints, &res)) != 0 || res == NULL) { + fprintf(stderr, "failed to resolve address:%s:%s:%s\n", host, port, + err != 0 ? gai_strerror(err) : "getaddrinfo returned NULL"); + return -1; + } + + memcpy(sa, res->ai_addr, res->ai_addrlen); + *salen = res->ai_addrlen; + + freeaddrinfo(res); + return 0; +} + +static void usage(const char *progname) +{ + printf("Usage: %s [options] [host]\n" + "Options:\n" + " -c specifies the certificate chain file (PEM format)\n" + " -k specifies the private key file (PEM format)\n" + " -p specifies the port number (default: 4433)\n" + " -h prints this help\n" + "\n" + "When both `-c` and `-k` is specified, runs as a server. Otherwise, runs as a\n" + "client connecting to host:port. If omitted, host defaults to 127.0.0.1.\n", + progname); + exit(0); +} + +static int is_server(void) +{ + return ctx.tls->certificates.count != 0; +} + +static int forward_stdin(quicly_conn_t *conn) +{ + quicly_stream_t *stream0; + char buf[4096]; + size_t rret; + + if ((stream0 = quicly_get_stream(conn, 0)) == NULL || !quicly_sendstate_is_open(&stream0->sendstate)) + return 0; + + while ((rret = read(0, buf, sizeof(buf))) == -1 && errno == EINTR) + ; + if (rret == 0) { + /* stdin closed, close the send-side of stream0 */ + quicly_streambuf_egress_shutdown(stream0); + return 0; + } else { + /* write data to send buffer */ + quicly_streambuf_egress_write(stream0, buf, rret); + return 1; + } +} + +static void on_stop_sending(quicly_stream_t *stream, int err) +{ + fprintf(stderr, "received STOP_SENDING: %" PRIu16 "\n", QUICLY_ERROR_GET_ERROR_CODE(err)); + quicly_close(stream->conn, QUICLY_ERROR_FROM_APPLICATION_ERROR_CODE(0), ""); +} + +static void on_receive_reset(quicly_stream_t *stream, int err) +{ + fprintf(stderr, "received RESET_STREAM: %" PRIu16 "\n", QUICLY_ERROR_GET_ERROR_CODE(err)); + quicly_close(stream->conn, QUICLY_ERROR_FROM_APPLICATION_ERROR_CODE(0), ""); +} + +static void on_receive(quicly_stream_t *stream, size_t off, const void *src, size_t len) +{ + /* read input to receive buffer */ + if (quicly_streambuf_ingress_receive(stream, off, src, len) != 0) + return; + + /* obtain contiguous bytes from the receive buffer */ + ptls_iovec_t input = quicly_streambuf_ingress_get(stream); + + if (is_server()) { + /* server: echo back to the client */ + if (quicly_sendstate_is_open(&stream->sendstate) && (input.len > 0)) { + quicly_streambuf_egress_write(stream, input.base, input.len); + /* shutdown the stream after echoing all data */ + if (quicly_recvstate_transfer_complete(&stream->recvstate)) + quicly_streambuf_egress_shutdown(stream); + } + } else { + /* client: print to stdout */ + fwrite(input.base, 1, input.len, stdout); + fflush(stdout); + /* initiate connection close after receiving all data */ + if (quicly_recvstate_transfer_complete(&stream->recvstate)) + quicly_close(stream->conn, 0, ""); + } + + /* remove used bytes from receive buffer */ + quicly_streambuf_ingress_shift(stream, input.len); +} + +static void process_msg(int is_client, quicly_conn_t **conns, struct msghdr *msg, size_t dgram_len) +{ + size_t off = 0, i; + + /* split UDP datagram into multiple QUIC packets */ + while (off < dgram_len) { + quicly_decoded_packet_t decoded; + if (quicly_decode_packet(&ctx, &decoded, msg->msg_iov[0].iov_base, dgram_len, &off) == SIZE_MAX) + return; + /* find the corresponding connection (TODO handle version negotiation, rebinding, retry, etc.) */ + for (i = 0; conns[i] != NULL; ++i) + if (quicly_is_destination(conns[i], NULL, msg->msg_name, &decoded)) + break; + if (conns[i] != NULL) { + /* let the current connection handle ingress packets */ + quicly_receive(conns[i], NULL, msg->msg_name, &decoded); + } else if (!is_client) { + /* assume that the packet is a new connection */ + quicly_accept(conns + i, &ctx, NULL, msg->msg_name, &decoded, NULL, &next_cid, NULL); + } + } +} + +static int send_one(int fd, struct sockaddr *dest, struct iovec *vec) +{ + struct msghdr mess = {.msg_name = dest, .msg_namelen = quicly_get_socklen(dest), .msg_iov = vec, .msg_iovlen = 1}; + int ret; + + while ((ret = (int)sendmsg(fd, &mess, 0)) == -1 && errno == EINTR) + ; + return ret; +} + +static int run_loop(int fd, quicly_conn_t *client) +{ + quicly_conn_t *conns[256] = {client}; /* a null-terminated list of connections; proper app should use a hashmap or something */ + size_t i; + int read_stdin = client != NULL; + + while (1) { + + /* wait for sockets to become readable, or some event in the QUIC stack to fire */ + fd_set readfds; + struct timeval tv; + do { + int64_t first_timeout = INT64_MAX, now = ctx.now->cb(ctx.now); + for (i = 0; conns[i] != NULL; ++i) { + int64_t conn_timeout = quicly_get_first_timeout(conns[i]); + if (conn_timeout < first_timeout) + first_timeout = conn_timeout; + } + if (now < first_timeout) { + int64_t delta = first_timeout - now; + if (delta > 1000 * 1000) + delta = 1000 * 1000; + tv.tv_sec = delta / 1000; + tv.tv_usec = (delta % 1000) * 1000; + } else { + tv.tv_sec = 1000; + tv.tv_usec = 0; + } + FD_ZERO(&readfds); + FD_SET(fd, &readfds); + /* we want to read input from stdin */ + if (read_stdin) + FD_SET(0, &readfds); + } while (select(fd + 1, &readfds, NULL, NULL, &tv) == -1 && errno == EINTR); + + /* read the QUIC fd */ + if (FD_ISSET(fd, &readfds)) { + uint8_t buf[4096]; + struct sockaddr_storage sa; + struct iovec vec = {.iov_base = buf, .iov_len = sizeof(buf)}; + struct msghdr msg = {.msg_name = &sa, .msg_namelen = sizeof(sa), .msg_iov = &vec, .msg_iovlen = 1}; + ssize_t rret; + while ((rret = recvmsg(fd, &msg, 0)) == -1 && errno == EINTR) + ; + if (rret > 0) + process_msg(client != NULL, conns, &msg, rret); + } + + /* read stdin, send the input to the active stram */ + if (FD_ISSET(0, &readfds)) { + assert(client != NULL); + if (!forward_stdin(client)) + read_stdin = 0; + } + + /* send QUIC packets, if any */ + for (i = 0; conns[i] != NULL; ++i) { + quicly_address_t dest, src; + struct iovec dgrams[10]; + uint8_t dgrams_buf[PTLS_ELEMENTSOF(dgrams) * ctx.transport_params.max_udp_payload_size]; + size_t num_dgrams = PTLS_ELEMENTSOF(dgrams); + int ret = quicly_send(conns[i], &dest, &src, dgrams, &num_dgrams, dgrams_buf, sizeof(dgrams_buf)); + switch (ret) { + case 0: { + size_t j; + for (j = 0; j != num_dgrams; ++j) { + send_one(fd, &dest.sa, &dgrams[j]); + } + } break; + case QUICLY_ERROR_FREE_CONNECTION: + /* connection has been closed, free, and exit when running as a client */ + quicly_free(conns[i]); + memmove(conns + i, conns + i + 1, sizeof(conns) - sizeof(conns[0]) * (i + 1)); + --i; + if (!is_server()) + return 0; + break; + default: + fprintf(stderr, "quicly_send returned %d\n", ret); + return 1; + } + } + } + + return 0; +} + +static int on_stream_open(quicly_stream_open_t *self, quicly_stream_t *stream) +{ + static const quicly_stream_callbacks_t stream_callbacks = { + quicly_streambuf_destroy, quicly_streambuf_egress_shift, quicly_streambuf_egress_emit, on_stop_sending, on_receive, + on_receive_reset}; + int ret; + + if ((ret = quicly_streambuf_create(stream, sizeof(quicly_streambuf_t))) != 0) + return ret; + stream->callbacks = &stream_callbacks; + return 0; +} + +int main(int argc, char **argv) +{ + ptls_openssl_sign_certificate_t sign_certificate; + ptls_context_t tlsctx = { + .random_bytes = ptls_openssl_random_bytes, + .get_time = &ptls_get_time, + .key_exchanges = ptls_openssl_key_exchanges, + .cipher_suites = ptls_openssl_cipher_suites, + }; + quicly_stream_open_t stream_open = {on_stream_open}; + char *host = "127.0.0.1", *port = "4433"; + struct sockaddr_storage sa; + socklen_t salen; + int ch, fd; + + /* setup quic context */ + ctx = quicly_spec_context; + ctx.tls = &tlsctx; + quicly_amend_ptls_context(ctx.tls); + ctx.stream_open = &stream_open; + + /* resolve command line options and arguments */ + while ((ch = getopt(argc, argv, "c:k:p:h")) != -1) { + switch (ch) { + case 'c': /* load certificate chain */ { + int ret; + if ((ret = ptls_load_certificates(&tlsctx, optarg)) != 0) { + fprintf(stderr, "failed to load certificates from file %s:%d\n", optarg, ret); + exit(1); + } + } break; + case 'k': /* load private key */ { + FILE *fp; + if ((fp = fopen(optarg, "r")) == NULL) { + fprintf(stderr, "failed to open file:%s:%s\n", optarg, strerror(errno)); + exit(1); + } + EVP_PKEY *pkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL); + fclose(fp); + if (pkey == NULL) { + fprintf(stderr, "failed to load private key from file:%s\n", optarg); + exit(1); + } + ptls_openssl_init_sign_certificate(&sign_certificate, pkey); + EVP_PKEY_free(pkey); + tlsctx.sign_certificate = &sign_certificate.super; + } break; + case 'p': /* port */ + port = optarg; + break; + case 'h': /* help */ + usage(argv[0]); + break; + default: + exit(1); + break; + } + } + if ((tlsctx.certificates.count != 0) != (tlsctx.sign_certificate != NULL)) { + fprintf(stderr, "-c and -k options must be used together\n"); + exit(1); + } + argc -= optind; + argv += optind; + if (argc != 0) + host = *argv++; + if (resolve_address((struct sockaddr *)&sa, &salen, host, port, AF_INET, SOCK_DGRAM, 0) != 0) + exit(1); + + /* open socket, on the specified port (as a server), or on any port (as a client) */ + if ((fd = socket(sa.ss_family, SOCK_DGRAM, 0)) == -1) { + perror("socket(2) failed"); + exit(1); + } + // fcntl(fd, F_SETFL, O_NONBLOCK); + if (is_server()) { + int reuseaddr = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)); + if (bind(fd, (struct sockaddr *)&sa, salen) != 0) { + perror("bind(2) failed"); + exit(1); + } + } else { + struct sockaddr_in local; + memset(&local, 0, sizeof(local)); + if (bind(fd, (struct sockaddr *)&local, sizeof(local)) != 0) { + perror("bind(2) failed"); + exit(1); + } + } + + quicly_conn_t *client = NULL; + if (!is_server()) { + /* initiate a connection, and open a stream */ + int ret; + if ((ret = quicly_connect(&client, &ctx, host, (struct sockaddr *)&sa, NULL, &next_cid, ptls_iovec_init(NULL, 0), NULL, + NULL)) != 0) { + fprintf(stderr, "quicly_connect failed:%d\n", ret); + exit(1); + } + quicly_stream_t *stream; /* we retain the opened stream via the on_stream_open callback */ + quicly_open_stream(client, &stream, 0); + } + + /* enter the event loop with a connection object */ + return run_loop(fd, client); +} From 0aa78f5b83b1b50d99866132d9cc88482bda1141 Mon Sep 17 00:00:00 2001 From: Jordi Cenzano Ferret Date: Thu, 1 Jul 2021 18:53:12 +0200 Subject: [PATCH 05/10] Added VSCode launch configs, very useful for debug --- .vscode/launch.json | 16 +-- examples/pipeclient.c | 235 +++++++++++++++--------------------------- examples/pipeserver.c | 159 +++++++++------------------- 3 files changed, 138 insertions(+), 272 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 2715e72e6..662726db6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,7 @@ "type": "cppdbg", "request": "launch", "program": "${workspaceRoot}/build/examples-pipeserver", - "args": ["-h"], + "args": ["-c", "../tmp/server.cert", "-k", "../tmp/server.key"], "stopAtEntry": false, "cwd": "${fileDirname}", "environment": [], @@ -20,26 +20,26 @@ "type": "cppdbg", "request": "launch", "program": "${workspaceRoot}/build/examples-pipeclient", - "args": ["-h"], + "args": [""], "stopAtEntry": false, "cwd": "${fileDirname}", "environment": [], - "externalConsole": false, + "externalConsole": true, "MIMode": "lldb" },{ - "name": "example-echo", + "name": "example-echo-client", "type": "cppdbg", "request": "launch", "program": "${workspaceRoot}/build/examples-echo", - "args": ["-h"], + "args": [""], "stopAtEntry": false, "cwd": "${fileDirname}", "environment": [], - "externalConsole": false, + "externalConsole": true, "MIMode": "lldb" }, { - "name": "cli", + "name": "cli help", "type": "cppdbg", "request": "launch", "program": "${workspaceRoot}/build/cli", @@ -51,4 +51,4 @@ "MIMode": "lldb" } ] -} \ No newline at end of file +} diff --git a/examples/pipeclient.c b/examples/pipeclient.c index a6723a0a1..1c29734f2 100644 --- a/examples/pipeclient.c +++ b/examples/pipeclient.c @@ -1,23 +1,6 @@ /* - * Copyright (c) 2019 Fastly, Kazuho Oku - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. + * Copyright (c) 2021 Jordi Cenzano + * Created from ./echo.c */ #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE 700 /* required for glibc to use getaddrinfo, etc. */ @@ -48,6 +31,10 @@ static quicly_context_t ctx; * CID seed */ static quicly_cid_plaintext_t next_cid; +/** + * Verbose mode + */ + int is_verbose = 0; static int resolve_address(struct sockaddr *sa, socklen_t *salen, const char *host, const char *port, int family, int type, int proto) @@ -77,34 +64,43 @@ static void usage(const char *progname) { printf("Usage: %s [options] [host]\n" "Options:\n" - " -c specifies the certificate chain file (PEM format)\n" - " -k specifies the private key file (PEM format)\n" + " -v Show messages from server" " -p specifies the port number (default: 4433)\n" " -h prints this help\n" "\n" - "When both `-c` and `-k` is specified, runs as a server. Otherwise, runs as a\n" - "client connecting to host:port. If omitted, host defaults to 127.0.0.1.\n", - progname); + "If omitted, host defaults to 127.0.0.1.\n" + "\n" + "Example (sends live video over QUIC):\n" + "ffmpeg -i \"udp://localhost:5000\" -c copy -f mpegts - | %s -p 4433 localhost\n", + progname, progname); exit(0); } -static int is_server(void) -{ - return ctx.tls->certificates.count != 0; -} - static int forward_stdin(quicly_conn_t *conn) { quicly_stream_t *stream0; - char buf[4096]; + const size_t READ_BLOCK_SIZE = 188 * 6; // Assumed input is transport stream + char buf[READ_BLOCK_SIZE]; size_t rret; if ((stream0 = quicly_get_stream(conn, 0)) == NULL || !quicly_sendstate_is_open(&stream0->sendstate)) return 0; - while ((rret = read(0, buf, sizeof(buf))) == -1 && errno == EINTR) + /* Read binary from stdin */ + while ((rret = read(STDIN_FILENO, buf, READ_BLOCK_SIZE)) == -1 && errno == EINTR) ; + + fprintf(stderr, "Read from stdin: %zu bytes\n", rret); + + // Something wrong! + if (rret < 0) { + // Show error and close the stream + fprintf(stderr, "failed to read from stdin"); + rret = 0; + } + if (rret == 0) { + fprintf(stderr, "Closing\n"); /* stdin closed, close the send-side of stream0 */ quicly_streambuf_egress_shutdown(stream0); return 0; @@ -136,46 +132,32 @@ static void on_receive(quicly_stream_t *stream, size_t off, const void *src, siz /* obtain contiguous bytes from the receive buffer */ ptls_iovec_t input = quicly_streambuf_ingress_get(stream); - if (is_server()) { - /* server: echo back to the client */ - if (quicly_sendstate_is_open(&stream->sendstate) && (input.len > 0)) { - quicly_streambuf_egress_write(stream, input.base, input.len); - /* shutdown the stream after echoing all data */ - if (quicly_recvstate_transfer_complete(&stream->recvstate)) - quicly_streambuf_egress_shutdown(stream); - } - } else { - /* client: print to stdout */ + /* print to stdout any data re receive from server*/ + if (is_verbose) { fwrite(input.base, 1, input.len, stdout); fflush(stdout); - /* initiate connection close after receiving all data */ - if (quicly_recvstate_transfer_complete(&stream->recvstate)) - quicly_close(stream->conn, 0, ""); } + /* initiate connection close after receiving all data */ + if (quicly_recvstate_transfer_complete(&stream->recvstate)) + quicly_close(stream->conn, 0, ""); /* remove used bytes from receive buffer */ quicly_streambuf_ingress_shift(stream, input.len); } -static void process_msg(int is_client, quicly_conn_t **conns, struct msghdr *msg, size_t dgram_len) +static void process_msg(quicly_conn_t *client, struct msghdr *msg, size_t dgram_len) { - size_t off = 0, i; + size_t off = 0; /* split UDP datagram into multiple QUIC packets */ while (off < dgram_len) { quicly_decoded_packet_t decoded; if (quicly_decode_packet(&ctx, &decoded, msg->msg_iov[0].iov_base, dgram_len, &off) == SIZE_MAX) return; - /* find the corresponding connection (TODO handle version negotiation, rebinding, retry, etc.) */ - for (i = 0; conns[i] != NULL; ++i) - if (quicly_is_destination(conns[i], NULL, msg->msg_name, &decoded)) - break; - if (conns[i] != NULL) { - /* let the current connection handle ingress packets */ - quicly_receive(conns[i], NULL, msg->msg_name, &decoded); - } else if (!is_client) { - /* assume that the packet is a new connection */ - quicly_accept(conns + i, &ctx, NULL, msg->msg_name, &decoded, NULL, &next_cid, NULL); + + if (client != NULL) { + if (quicly_is_destination(client, NULL, msg->msg_name, &decoded)) + quicly_receive(client, NULL, msg->msg_name, &decoded); } } } @@ -190,24 +172,19 @@ static int send_one(int fd, struct sockaddr *dest, struct iovec *vec) return ret; } -static int run_loop(int fd, quicly_conn_t *client) +static int run_loop_client(int fd, quicly_conn_t *client) { - quicly_conn_t *conns[256] = {client}; /* a null-terminated list of connections; proper app should use a hashmap or something */ - size_t i; - int read_stdin = client != NULL; - - while (1) { + int read_stdin = 1; + while (1) { /* wait for sockets to become readable, or some event in the QUIC stack to fire */ fd_set readfds; struct timeval tv; do { int64_t first_timeout = INT64_MAX, now = ctx.now->cb(ctx.now); - for (i = 0; conns[i] != NULL; ++i) { - int64_t conn_timeout = quicly_get_first_timeout(conns[i]); - if (conn_timeout < first_timeout) - first_timeout = conn_timeout; - } + int64_t conn_timeout = quicly_get_first_timeout(client); + if (conn_timeout < first_timeout) + first_timeout = conn_timeout; if (now < first_timeout) { int64_t delta = first_timeout - now; if (delta > 1000 * 1000) @@ -220,9 +197,8 @@ static int run_loop(int fd, quicly_conn_t *client) } FD_ZERO(&readfds); FD_SET(fd, &readfds); - /* we want to read input from stdin */ if (read_stdin) - FD_SET(0, &readfds); + FD_SET(STDIN_FILENO, &readfds); } while (select(fd + 1, &readfds, NULL, NULL, &tv) == -1 && errno == EINTR); /* read the QUIC fd */ @@ -235,10 +211,9 @@ static int run_loop(int fd, quicly_conn_t *client) while ((rret = recvmsg(fd, &msg, 0)) == -1 && errno == EINTR) ; if (rret > 0) - process_msg(client != NULL, conns, &msg, rret); + process_msg(client, &msg, rret); } - - /* read stdin, send the input to the active stram */ + if (FD_ISSET(0, &readfds)) { assert(client != NULL); if (!forward_stdin(client)) @@ -246,31 +221,25 @@ static int run_loop(int fd, quicly_conn_t *client) } /* send QUIC packets, if any */ - for (i = 0; conns[i] != NULL; ++i) { - quicly_address_t dest, src; - struct iovec dgrams[10]; - uint8_t dgrams_buf[PTLS_ELEMENTSOF(dgrams) * ctx.transport_params.max_udp_payload_size]; - size_t num_dgrams = PTLS_ELEMENTSOF(dgrams); - int ret = quicly_send(conns[i], &dest, &src, dgrams, &num_dgrams, dgrams_buf, sizeof(dgrams_buf)); - switch (ret) { - case 0: { - size_t j; - for (j = 0; j != num_dgrams; ++j) { - send_one(fd, &dest.sa, &dgrams[j]); - } - } break; - case QUICLY_ERROR_FREE_CONNECTION: - /* connection has been closed, free, and exit when running as a client */ - quicly_free(conns[i]); - memmove(conns + i, conns + i + 1, sizeof(conns) - sizeof(conns[0]) * (i + 1)); - --i; - if (!is_server()) - return 0; - break; - default: - fprintf(stderr, "quicly_send returned %d\n", ret); - return 1; + quicly_address_t dest, src; + struct iovec dgrams[10]; + uint8_t dgrams_buf[PTLS_ELEMENTSOF(dgrams) * ctx.transport_params.max_udp_payload_size]; + size_t num_dgrams = PTLS_ELEMENTSOF(dgrams); + int ret = quicly_send(client, &dest, &src, dgrams, &num_dgrams, dgrams_buf, sizeof(dgrams_buf)); + switch (ret) { + case 0: { + size_t j; + for (j = 0; j != num_dgrams; ++j) { + send_one(fd, &dest.sa, &dgrams[j]); } + } break; + case QUICLY_ERROR_FREE_CONNECTION: + /* connection has been closed, free, and exit when running as a client */ + quicly_free(client); + return 0; + default: + fprintf(stderr, "quicly_send returned %d\n", ret); + return 1; } } @@ -292,7 +261,6 @@ static int on_stream_open(quicly_stream_open_t *self, quicly_stream_t *stream) int main(int argc, char **argv) { - ptls_openssl_sign_certificate_t sign_certificate; ptls_context_t tlsctx = { .random_bytes = ptls_openssl_random_bytes, .get_time = &ptls_get_time, @@ -312,34 +280,14 @@ int main(int argc, char **argv) ctx.stream_open = &stream_open; /* resolve command line options and arguments */ - while ((ch = getopt(argc, argv, "c:k:p:h")) != -1) { + while ((ch = getopt(argc, argv, "p:h:v")) != -1) { switch (ch) { - case 'c': /* load certificate chain */ { - int ret; - if ((ret = ptls_load_certificates(&tlsctx, optarg)) != 0) { - fprintf(stderr, "failed to load certificates from file %s:%d\n", optarg, ret); - exit(1); - } - } break; - case 'k': /* load private key */ { - FILE *fp; - if ((fp = fopen(optarg, "r")) == NULL) { - fprintf(stderr, "failed to open file:%s:%s\n", optarg, strerror(errno)); - exit(1); - } - EVP_PKEY *pkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL); - fclose(fp); - if (pkey == NULL) { - fprintf(stderr, "failed to load private key from file:%s\n", optarg); - exit(1); - } - ptls_openssl_init_sign_certificate(&sign_certificate, pkey); - EVP_PKEY_free(pkey); - tlsctx.sign_certificate = &sign_certificate.super; - } break; case 'p': /* port */ port = optarg; break; + case 'v': /* verbose */ + is_verbose = 1; + break; case 'h': /* help */ usage(argv[0]); break; @@ -348,10 +296,6 @@ int main(int argc, char **argv) break; } } - if ((tlsctx.certificates.count != 0) != (tlsctx.sign_certificate != NULL)) { - fprintf(stderr, "-c and -k options must be used together\n"); - exit(1); - } argc -= optind; argv += optind; if (argc != 0) @@ -359,41 +303,30 @@ int main(int argc, char **argv) if (resolve_address((struct sockaddr *)&sa, &salen, host, port, AF_INET, SOCK_DGRAM, 0) != 0) exit(1); - /* open socket, on the specified port (as a server), or on any port (as a client) */ + /* open socket on any port (as a client) */ if ((fd = socket(sa.ss_family, SOCK_DGRAM, 0)) == -1) { perror("socket(2) failed"); exit(1); } // fcntl(fd, F_SETFL, O_NONBLOCK); - if (is_server()) { - int reuseaddr = 1; - setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)); - if (bind(fd, (struct sockaddr *)&sa, salen) != 0) { - perror("bind(2) failed"); - exit(1); - } - } else { - struct sockaddr_in local; - memset(&local, 0, sizeof(local)); - if (bind(fd, (struct sockaddr *)&local, sizeof(local)) != 0) { - perror("bind(2) failed"); - exit(1); - } + struct sockaddr_in local; + memset(&local, 0, sizeof(local)); + if (bind(fd, (struct sockaddr *)&local, sizeof(local)) != 0) { + perror("bind(2) failed"); + exit(1); } quicly_conn_t *client = NULL; - if (!is_server()) { - /* initiate a connection, and open a stream */ - int ret; - if ((ret = quicly_connect(&client, &ctx, host, (struct sockaddr *)&sa, NULL, &next_cid, ptls_iovec_init(NULL, 0), NULL, - NULL)) != 0) { - fprintf(stderr, "quicly_connect failed:%d\n", ret); - exit(1); - } - quicly_stream_t *stream; /* we retain the opened stream via the on_stream_open callback */ - quicly_open_stream(client, &stream, 0); + /* initiate a connection, and open a stream */ + int ret; + if ((ret = quicly_connect(&client, &ctx, host, (struct sockaddr *)&sa, NULL, &next_cid, ptls_iovec_init(NULL, 0), NULL, + NULL)) != 0) { + fprintf(stderr, "quicly_connect failed:%d\n", ret); + exit(1); } + quicly_stream_t *stream; /* we retain the opened stream via the on_stream_open callback */ + quicly_open_stream(client, &stream, 0); /* enter the event loop with a connection object */ - return run_loop(fd, client); + return run_loop_client(fd, client); } diff --git a/examples/pipeserver.c b/examples/pipeserver.c index a6723a0a1..4fa06a475 100644 --- a/examples/pipeserver.c +++ b/examples/pipeserver.c @@ -1,23 +1,6 @@ /* - * Copyright (c) 2019 Fastly, Kazuho Oku - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. + * Copyright (c) 2021 Jordi Cenzano + * Created from ./echo.c */ #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE 700 /* required for glibc to use getaddrinfo, etc. */ @@ -48,6 +31,11 @@ static quicly_context_t ctx; * CID seed */ static quicly_cid_plaintext_t next_cid; +/** + * Verbose mode + */ + int is_verbose = 0; + static int resolve_address(struct sockaddr *sa, socklen_t *salen, const char *host, const char *port, int family, int type, int proto) @@ -82,39 +70,16 @@ static void usage(const char *progname) " -p specifies the port number (default: 4433)\n" " -h prints this help\n" "\n" - "When both `-c` and `-k` is specified, runs as a server. Otherwise, runs as a\n" - "client connecting to host:port. If omitted, host defaults to 127.0.0.1.\n", - progname); + "`-c` and `-k` have to be be specified\n" + "If omitted, host defaults to 127.0.0.1.\n" + "In this case all info received will be output to stdout, so you should have only 1 connection active\n" + "\n" + "Example (receives live video over QUIC):\n" + "%s -c server.crt -k server.key -p 4433 | ffplay -i -\n", + progname, progname); exit(0); } -static int is_server(void) -{ - return ctx.tls->certificates.count != 0; -} - -static int forward_stdin(quicly_conn_t *conn) -{ - quicly_stream_t *stream0; - char buf[4096]; - size_t rret; - - if ((stream0 = quicly_get_stream(conn, 0)) == NULL || !quicly_sendstate_is_open(&stream0->sendstate)) - return 0; - - while ((rret = read(0, buf, sizeof(buf))) == -1 && errno == EINTR) - ; - if (rret == 0) { - /* stdin closed, close the send-side of stream0 */ - quicly_streambuf_egress_shutdown(stream0); - return 0; - } else { - /* write data to send buffer */ - quicly_streambuf_egress_write(stream0, buf, rret); - return 1; - } -} - static void on_stop_sending(quicly_stream_t *stream, int err) { fprintf(stderr, "received STOP_SENDING: %" PRIu16 "\n", QUICLY_ERROR_GET_ERROR_CODE(err)); @@ -136,28 +101,28 @@ static void on_receive(quicly_stream_t *stream, size_t off, const void *src, siz /* obtain contiguous bytes from the receive buffer */ ptls_iovec_t input = quicly_streambuf_ingress_get(stream); - if (is_server()) { - /* server: echo back to the client */ - if (quicly_sendstate_is_open(&stream->sendstate) && (input.len > 0)) { - quicly_streambuf_egress_write(stream, input.base, input.len); - /* shutdown the stream after echoing all data */ - if (quicly_recvstate_transfer_complete(&stream->recvstate)) - quicly_streambuf_egress_shutdown(stream); - } - } else { - /* client: print to stdout */ + /* server: echo back to the client */ + if (quicly_sendstate_is_open(&stream->sendstate) && (input.len > 0)) { + // Write received data to stdout fwrite(input.base, 1, input.len, stdout); fflush(stdout); - /* initiate connection close after receiving all data */ + + // Show the received size to logs and send it to the client + char str[128]; + sprintf(str, "Received: %zu bytes\n", input.len); + if (is_verbose) + fprintf(stderr, "%s", str); + quicly_streambuf_egress_write(stream, str, strlen(str)); + // shutdown the stream after echoing all data if (quicly_recvstate_transfer_complete(&stream->recvstate)) - quicly_close(stream->conn, 0, ""); + quicly_streambuf_egress_shutdown(stream); } /* remove used bytes from receive buffer */ quicly_streambuf_ingress_shift(stream, input.len); } -static void process_msg(int is_client, quicly_conn_t **conns, struct msghdr *msg, size_t dgram_len) +static void process_msg(quicly_conn_t **conns, struct msghdr *msg, size_t dgram_len) { size_t off = 0, i; @@ -173,7 +138,7 @@ static void process_msg(int is_client, quicly_conn_t **conns, struct msghdr *msg if (conns[i] != NULL) { /* let the current connection handle ingress packets */ quicly_receive(conns[i], NULL, msg->msg_name, &decoded); - } else if (!is_client) { + } else { /* assume that the packet is a new connection */ quicly_accept(conns + i, &ctx, NULL, msg->msg_name, &decoded, NULL, &next_cid, NULL); } @@ -190,11 +155,10 @@ static int send_one(int fd, struct sockaddr *dest, struct iovec *vec) return ret; } -static int run_loop(int fd, quicly_conn_t *client) +static int run_loop_server(int fd) { - quicly_conn_t *conns[256] = {client}; /* a null-terminated list of connections; proper app should use a hashmap or something */ + quicly_conn_t *conns[256] = {NULL}; /* a null-terminated list of connections; proper app should use a hashmap or something */ size_t i; - int read_stdin = client != NULL; while (1) { @@ -220,9 +184,6 @@ static int run_loop(int fd, quicly_conn_t *client) } FD_ZERO(&readfds); FD_SET(fd, &readfds); - /* we want to read input from stdin */ - if (read_stdin) - FD_SET(0, &readfds); } while (select(fd + 1, &readfds, NULL, NULL, &tv) == -1 && errno == EINTR); /* read the QUIC fd */ @@ -235,14 +196,7 @@ static int run_loop(int fd, quicly_conn_t *client) while ((rret = recvmsg(fd, &msg, 0)) == -1 && errno == EINTR) ; if (rret > 0) - process_msg(client != NULL, conns, &msg, rret); - } - - /* read stdin, send the input to the active stram */ - if (FD_ISSET(0, &readfds)) { - assert(client != NULL); - if (!forward_stdin(client)) - read_stdin = 0; + process_msg(conns, &msg, rret); } /* send QUIC packets, if any */ @@ -260,12 +214,10 @@ static int run_loop(int fd, quicly_conn_t *client) } } break; case QUICLY_ERROR_FREE_CONNECTION: - /* connection has been closed, free, and exit when running as a client */ + /* connection has been closed, free */ quicly_free(conns[i]); memmove(conns + i, conns + i + 1, sizeof(conns) - sizeof(conns[0]) * (i + 1)); --i; - if (!is_server()) - return 0; break; default: fprintf(stderr, "quicly_send returned %d\n", ret); @@ -312,7 +264,7 @@ int main(int argc, char **argv) ctx.stream_open = &stream_open; /* resolve command line options and arguments */ - while ((ch = getopt(argc, argv, "c:k:p:h")) != -1) { + while ((ch = getopt(argc, argv, "c:k:p:h:v")) != -1) { switch (ch) { case 'c': /* load certificate chain */ { int ret; @@ -340,6 +292,9 @@ int main(int argc, char **argv) case 'p': /* port */ port = optarg; break; + case 'v': /* verbose */ + is_verbose = 1; + break; case 'h': /* help */ usage(argv[0]); break; @@ -348,8 +303,8 @@ int main(int argc, char **argv) break; } } - if ((tlsctx.certificates.count != 0) != (tlsctx.sign_certificate != NULL)) { - fprintf(stderr, "-c and -k options must be used together\n"); + if ((tlsctx.certificates.count <= 0) || (tlsctx.sign_certificate == NULL)) { + fprintf(stderr, "-c and -k options must be used\n"); exit(1); } argc -= optind; @@ -359,41 +314,19 @@ int main(int argc, char **argv) if (resolve_address((struct sockaddr *)&sa, &salen, host, port, AF_INET, SOCK_DGRAM, 0) != 0) exit(1); - /* open socket, on the specified port (as a server), or on any port (as a client) */ + /* open socket, on the specified port (as a server) */ if ((fd = socket(sa.ss_family, SOCK_DGRAM, 0)) == -1) { perror("socket(2) failed"); exit(1); } // fcntl(fd, F_SETFL, O_NONBLOCK); - if (is_server()) { - int reuseaddr = 1; - setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)); - if (bind(fd, (struct sockaddr *)&sa, salen) != 0) { - perror("bind(2) failed"); - exit(1); - } - } else { - struct sockaddr_in local; - memset(&local, 0, sizeof(local)); - if (bind(fd, (struct sockaddr *)&local, sizeof(local)) != 0) { - perror("bind(2) failed"); - exit(1); - } - } - - quicly_conn_t *client = NULL; - if (!is_server()) { - /* initiate a connection, and open a stream */ - int ret; - if ((ret = quicly_connect(&client, &ctx, host, (struct sockaddr *)&sa, NULL, &next_cid, ptls_iovec_init(NULL, 0), NULL, - NULL)) != 0) { - fprintf(stderr, "quicly_connect failed:%d\n", ret); - exit(1); - } - quicly_stream_t *stream; /* we retain the opened stream via the on_stream_open callback */ - quicly_open_stream(client, &stream, 0); + int reuseaddr = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)); + if (bind(fd, (struct sockaddr *)&sa, salen) != 0) { + perror("bind(2) failed"); + exit(1); } - + /* enter the event loop with a connection object */ - return run_loop(fd, client); + return run_loop_server(fd); } From ab45f1bd211cdc121c5527911b953ef769b46a32 Mon Sep 17 00:00:00 2001 From: Jordi Cenzano Ferret Date: Thu, 1 Jul 2021 18:56:19 +0200 Subject: [PATCH 06/10] Added VSCode settings file --- .vscode/settings.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..3bd8ac75c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "files.associations": { + "unistd.h": "c", + "sstream": "c" + } +} \ No newline at end of file From 3e198fd9d69daebe5a128f459b67ef540281df29 Mon Sep 17 00:00:00 2001 From: Jordi Cenzano Ferret Date: Thu, 1 Jul 2021 19:20:14 +0200 Subject: [PATCH 07/10] Add readme for pipe example --- examples/PIPE-README.md | 53 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 examples/PIPE-README.md diff --git a/examples/PIPE-README.md b/examples/PIPE-README.md new file mode 100644 index 000000000..7f71daf40 --- /dev/null +++ b/examples/PIPE-README.md @@ -0,0 +1,53 @@ +Send data streams over QUIC (Quicly) +=== + +Using the `pipeclient` and `pipeserver` binaries you can esaly send data streams (ex: live video) over QUIC + +How to test it +--- + +- Install & compile the code, you can follow the [readme](../README.md) instructions + +- Generate the certs in the `/tmp` dir: +``` +mkdir -p tmp +openssl req -nodes -new -x509 -keyout tmp/server.key -out tmp/server.crt +``` + +- To run the server +``` +./examples-pipeserver -c ../tmp/server.crt -k ../tmp/server.key -p 4433 > myReceivedFile.bin +``` + +- To run the client +``` +cat myfile.bin | ./examples-pipeclient +``` + +- Check files +``` +diff myfile.bin myReceivedFile.bin +``` + +Example with video +--- + +- To run the server: +``` +./examples-pipeserver -c ../tmp/server.cert -k ../tmp/server.key -p 4433 | ffplay - +``` + +- To run the client (*): +``` +ffmpeg -re -f lavfi -i smptebars=duration=30:size=320x200:rate=30 -f lavfi -re -i sine=frequency=1000:duration=30:sample_rate=48000 -pix_fmt yuv420p -c:v libx264 -b:v 180k -g 60 -keyint_min 60 -profile:v baseline -preset veryfast -c:a aac -b:a 96k -vf "drawtext=fontfile=/Library/Fonts/Arial.ttf: text=\'Local time %{localtime\: %Y\/%m\/%d %H.%M.%S} (%{n})\': x=10: y=10: fontsize=16: fontcolor=white: box=1: boxcolor=0x00000099" -f mpegts - | ./examples-pipeclient +``` + +The previous command will work on MACos, but if you have problems with `` or with the fonts path you can use the following one: +``` +ffmpeg -re -i muVideoFile.mp4 -c copy -f mpegts - | ./examples-pipeclient +``` + +You should see live video!!! + + +Note: Assumes `ffmpeg` and `ffplay` installed \ No newline at end of file From da594b1defdf9f2340f566cd40fccbb65e6cb201 Mon Sep 17 00:00:00 2001 From: Jordi Cenzano Ferret Date: Thu, 1 Jul 2021 20:12:13 +0200 Subject: [PATCH 08/10] Modify the pipe server to accept a single connection to avoid data interfierece --- examples/pipeserver.c | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/examples/pipeserver.c b/examples/pipeserver.c index 4fa06a475..9aceff3d5 100644 --- a/examples/pipeserver.c +++ b/examples/pipeserver.c @@ -122,9 +122,9 @@ static void on_receive(quicly_stream_t *stream, size_t off, const void *src, siz quicly_streambuf_ingress_shift(stream, input.len); } -static void process_msg(quicly_conn_t **conns, struct msghdr *msg, size_t dgram_len) +static void process_msg(quicly_conn_t **conn, struct msghdr *msg, size_t dgram_len) { - size_t off = 0, i; + size_t off = 0; /* split UDP datagram into multiple QUIC packets */ while (off < dgram_len) { @@ -132,15 +132,15 @@ static void process_msg(quicly_conn_t **conns, struct msghdr *msg, size_t dgram_ if (quicly_decode_packet(&ctx, &decoded, msg->msg_iov[0].iov_base, dgram_len, &off) == SIZE_MAX) return; /* find the corresponding connection (TODO handle version negotiation, rebinding, retry, etc.) */ - for (i = 0; conns[i] != NULL; ++i) - if (quicly_is_destination(conns[i], NULL, msg->msg_name, &decoded)) - break; - if (conns[i] != NULL) { - /* let the current connection handle ingress packets */ - quicly_receive(conns[i], NULL, msg->msg_name, &decoded); + if (*conn != NULL) { + if (quicly_is_destination(*conn, NULL, msg->msg_name, &decoded)) { + quicly_receive(*conn, NULL, msg->msg_name, &decoded); + } else { + fprintf(stderr, "failed to accept new incoming connection, this server only allows 1 concurrent connection\n"); + } } else { /* assume that the packet is a new connection */ - quicly_accept(conns + i, &ctx, NULL, msg->msg_name, &decoded, NULL, &next_cid, NULL); + quicly_accept(conn, &ctx, NULL, msg->msg_name, &decoded, NULL, &next_cid, NULL); } } } @@ -157,8 +157,7 @@ static int send_one(int fd, struct sockaddr *dest, struct iovec *vec) static int run_loop_server(int fd) { - quicly_conn_t *conns[256] = {NULL}; /* a null-terminated list of connections; proper app should use a hashmap or something */ - size_t i; + quicly_conn_t *conn = NULL; /* this server only accepts a single connection */ while (1) { @@ -167,8 +166,8 @@ static int run_loop_server(int fd) struct timeval tv; do { int64_t first_timeout = INT64_MAX, now = ctx.now->cb(ctx.now); - for (i = 0; conns[i] != NULL; ++i) { - int64_t conn_timeout = quicly_get_first_timeout(conns[i]); + if (conn != NULL) { + int64_t conn_timeout = quicly_get_first_timeout(conn); if (conn_timeout < first_timeout) first_timeout = conn_timeout; } @@ -196,16 +195,16 @@ static int run_loop_server(int fd) while ((rret = recvmsg(fd, &msg, 0)) == -1 && errno == EINTR) ; if (rret > 0) - process_msg(conns, &msg, rret); + process_msg(&conn, &msg, rret); } /* send QUIC packets, if any */ - for (i = 0; conns[i] != NULL; ++i) { + if (conn != NULL) { quicly_address_t dest, src; struct iovec dgrams[10]; uint8_t dgrams_buf[PTLS_ELEMENTSOF(dgrams) * ctx.transport_params.max_udp_payload_size]; size_t num_dgrams = PTLS_ELEMENTSOF(dgrams); - int ret = quicly_send(conns[i], &dest, &src, dgrams, &num_dgrams, dgrams_buf, sizeof(dgrams_buf)); + int ret = quicly_send(conn, &dest, &src, dgrams, &num_dgrams, dgrams_buf, sizeof(dgrams_buf)); switch (ret) { case 0: { size_t j; @@ -215,9 +214,8 @@ static int run_loop_server(int fd) } break; case QUICLY_ERROR_FREE_CONNECTION: /* connection has been closed, free */ - quicly_free(conns[i]); - memmove(conns + i, conns + i + 1, sizeof(conns) - sizeof(conns[0]) * (i + 1)); - --i; + quicly_free(conn); + conn = NULL; break; default: fprintf(stderr, "quicly_send returned %d\n", ret); From b2968b0eaaf33a6b7af66dea96d9865cef87e397 Mon Sep 17 00:00:00 2001 From: Jordi Cenzano Ferret Date: Thu, 1 Jul 2021 20:17:33 +0200 Subject: [PATCH 09/10] Better readme for pipe example --- examples/PIPE-README.md | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/examples/PIPE-README.md b/examples/PIPE-README.md index 7f71daf40..eb391c94a 100644 --- a/examples/PIPE-README.md +++ b/examples/PIPE-README.md @@ -29,7 +29,7 @@ cat myfile.bin | ./examples-pipeclient diff myfile.bin myReceivedFile.bin ``` -Example with video +Example with video (Assumes `ffmpeg` and `ffplay` installed) --- - To run the server: @@ -37,17 +37,14 @@ Example with video ./examples-pipeserver -c ../tmp/server.cert -k ../tmp/server.key -p 4433 | ffplay - ``` -- To run the client (*): +- To run the client: ``` ffmpeg -re -f lavfi -i smptebars=duration=30:size=320x200:rate=30 -f lavfi -re -i sine=frequency=1000:duration=30:sample_rate=48000 -pix_fmt yuv420p -c:v libx264 -b:v 180k -g 60 -keyint_min 60 -profile:v baseline -preset veryfast -c:a aac -b:a 96k -vf "drawtext=fontfile=/Library/Fonts/Arial.ttf: text=\'Local time %{localtime\: %Y\/%m\/%d %H.%M.%S} (%{n})\': x=10: y=10: fontsize=16: fontcolor=white: box=1: boxcolor=0x00000099" -f mpegts - | ./examples-pipeclient ``` -The previous command will work on MACos, but if you have problems with `` or with the fonts path you can use the following one: +Note: The previous command will work on MACos, but if you have problems with `lavfi` filter or with the fonts path you can use the following one: ``` -ffmpeg -re -i muVideoFile.mp4 -c copy -f mpegts - | ./examples-pipeclient +ffmpeg -re -i myVideoFile.mp4 -c copy -f mpegts - | ./examples-pipeclient ``` -You should see live video!!! - - -Note: Assumes `ffmpeg` and `ffplay` installed \ No newline at end of file +Now you should see live video!!! \ No newline at end of file From 22c32d2320e60835ca4e170c60030f359a22e57f Mon Sep 17 00:00:00 2001 From: Jordi Cenzano Ferret Date: Thu, 1 Jul 2021 20:37:56 +0200 Subject: [PATCH 10/10] Added pipe examples binaries to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index ac0fbb831..820b22de0 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ quicly-probes.h quicly-tracer.h test.t udpfw +examples-pipeclient +examples-pipeserver