diff --git a/README.md b/README.md index 780624c..bd74470 100644 --- a/README.md +++ b/README.md @@ -7,18 +7,19 @@ Uses https://github.com/h2o/quicly Usage: ./qperf [options] Options: + -c target run as client and connect to target server + -e measure time for connection establishment and first byte only + -g enable UDP generic segmentation offload -p port to listen on/connect to (default 18080) -s run as server - -c target run as client and connect to target server -t time (s) run for X seconds (default 10s) - -e measure time for connection establishment and first byte only -h print this help ``` server ``` ./qperf -s -starting server on port 18080 +starting server with pid 5624 on port 18080 got new connection request received, sending data connection 0 second 0 send window: 1112923 packets sent: 364792 packets lost: 373 diff --git a/client.c b/client.c index bfc0184..c3200d9 100755 --- a/client.c +++ b/client.c @@ -100,7 +100,7 @@ void enqueue_request(quicly_conn_t *conn) quicly_streambuf_egress_shutdown(stream); } -static void client_on_conn_close(quicly_closed_by_peer_t *self, quicly_conn_t *conn, int err, +static void client_on_conn_close(quicly_closed_by_remote_t *self, quicly_conn_t *conn, int err, uint64_t frame_type, const char *reason, size_t reason_len) { if (QUICLY_ERROR_IS_QUIC_TRANSPORT(err)) { @@ -117,9 +117,9 @@ static void client_on_conn_close(quicly_closed_by_peer_t *self, quicly_conn_t *c } static quicly_stream_open_t stream_open = {&client_on_stream_open}; -static quicly_closed_by_peer_t closed_by_peer = {&client_on_conn_close}; +static quicly_closed_by_remote_t closed_by_remote = {&client_on_conn_close}; -int run_client(const char *port, const char *host, int runtime_s, bool ttfb_only) +int run_client(const char *port, bool gso, const char *host, int runtime_s, bool ttfb_only) { printf("running client with host=%s, port=%s and runtime=%is\n", host, port, runtime_s); quit_after_first_byte = ttfb_only; @@ -127,11 +127,15 @@ int run_client(const char *port, const char *host, int runtime_s, bool ttfb_only client_ctx = quicly_spec_context; client_ctx.tls = get_tlsctx(); client_ctx.stream_open = &stream_open; - client_ctx.closed_by_peer = &closed_by_peer; + client_ctx.closed_by_remote = &closed_by_remote; client_ctx.transport_params.max_stream_data.uni = UINT32_MAX; client_ctx.transport_params.max_stream_data.bidi_local = UINT32_MAX; client_ctx.transport_params.max_stream_data.bidi_remote = UINT32_MAX; + if (gso) { + enable_gso(); + } + setup_session_cache(get_tlsctx()); quicly_amend_ptls_context(get_tlsctx()); diff --git a/client.h b/client.h index b0ee6d4..bb7122c 100755 --- a/client.h +++ b/client.h @@ -3,8 +3,7 @@ #include #include -int run_client(const char* port, const char *host, int runtime_s, bool ttfb_only); -void quit_client(); +int run_client(const char* port, bool gso, const char *host, int runtime_s, bool ttfb_only); void quit_client(); void on_first_byte(); diff --git a/common.c b/common.c index e2d8aa2..42cc217 100644 --- a/common.c +++ b/common.c @@ -1,12 +1,12 @@ #include "common.h" #include +#include #include #include #include #include - ptls_context_t *get_tlsctx() { static ptls_context_t tlsctx = {.random_bytes = ptls_openssl_random_bytes, @@ -35,15 +35,89 @@ struct addrinfo *get_address(const char *host, const char *port) } } +bool send_dgrams_default(int fd, struct sockaddr *dest, struct iovec *dgrams, size_t num_dgrams) +{ + for(size_t i = 0; i < num_dgrams; ++i) { + struct msghdr mess = { + .msg_name = dest, + .msg_namelen = quicly_get_socklen(dest), + .msg_iov = &dgrams[i], .msg_iovlen = 1 + }; + + ssize_t bytes_sent; + while ((bytes_sent = sendmsg(fd, &mess, 0)) == -1 && errno == EINTR); + if (bytes_sent == -1) { + perror("sendmsg failed"); + return false; + } + } + + return true; +} + +#ifdef __linux__ + /* UDP GSO is only supported on linux */ + #ifndef UDP_SEGMENT + #define UDP_SEGMENT 103 /* Set GSO segmentation size */ + #endif + +bool send_dgrams_gso(int fd, struct sockaddr *dest, struct iovec *dgrams, size_t num_dgrams) +{ + struct iovec vec = { + .iov_base = (void *)dgrams[0].iov_base, + .iov_len = dgrams[num_dgrams - 1].iov_base + dgrams[num_dgrams - 1].iov_len - dgrams[0].iov_base + }; + + struct msghdr mess = { + .msg_name = dest, + .msg_namelen = quicly_get_socklen(dest), + .msg_iov = &vec, + .msg_iovlen = 1 + }; + + union { + struct cmsghdr hdr; + char buf[CMSG_SPACE(sizeof(uint16_t))]; + } cmsg; + if (num_dgrams != 1) { + cmsg.hdr.cmsg_level = SOL_UDP; + cmsg.hdr.cmsg_type = UDP_SEGMENT; + cmsg.hdr.cmsg_len = CMSG_LEN(sizeof(uint16_t)); + *(uint16_t *)CMSG_DATA(&cmsg.hdr) = dgrams[0].iov_len; + mess.msg_control = &cmsg; + mess.msg_controllen = (socklen_t)CMSG_SPACE(sizeof(uint16_t)); + } + + ssize_t bytes_sent; + while ((bytes_sent = sendmsg(fd, &mess, 0)) == -1 && errno == EINTR); + if (bytes_sent == -1) { + perror("sendmsg failed"); + return false; + } + + return true; +} + +#endif + +bool (*send_dgrams)(int fd, struct sockaddr *dest, struct iovec *dgrams, size_t num_dgrams) = send_dgrams_default; + +void enable_gso() +{ + send_dgrams = send_dgrams_gso; +} + bool send_pending(quicly_context_t *ctx, int fd, quicly_conn_t *conn) { -#define SEND_BATCH_SIZE 16 + #define SEND_BATCH_SIZE 16 - quicly_datagram_t *packets[SEND_BATCH_SIZE]; + quicly_address_t dest, src; + struct iovec dgrams[SEND_BATCH_SIZE]; + uint8_t dgrams_buf[SEND_BATCH_SIZE * ctx->transport_params.max_udp_payload_size]; + size_t num_dgrams = SEND_BATCH_SIZE; while(true) { - size_t packet_count = SEND_BATCH_SIZE; - int quicly_res = quicly_send(conn, packets, &packet_count); + int quicly_res = quicly_send(conn, &dest, &src, dgrams, &num_dgrams, &dgrams_buf, sizeof(dgrams_buf)); if(quicly_res != 0) { if(quicly_res != QUICLY_ERROR_FREE_CONNECTION) { printf("quicly_send failed with code %i\n", quicly_res); @@ -51,24 +125,16 @@ bool send_pending(quicly_context_t *ctx, int fd, quicly_conn_t *conn) printf("connection closed\n"); } return false; - } else if(packet_count == 0) { + } else if(num_dgrams == 0) { return true; } - for(size_t i = 0; i < packet_count; ++i) { - quicly_datagram_t *packet = packets[i]; - ssize_t bytes_sent = sendto(fd, packet->data.base, packet->data.len, 0, - &packet->dest.sa, quicly_get_socklen(&packet->dest.sa)); - ctx->packet_allocator->free_packet(ctx->packet_allocator, packets[i]); - if(bytes_sent == -1) { - perror("sendto failed"); - return false; - } + if (!send_dgrams(fd, &dest.sa, dgrams, num_dgrams)) { + return false; } }; } - void print_escaped(const char *src, size_t len) { for(size_t i = 0; i < len; ++i) { diff --git a/common.h b/common.h index 5dbb34b..280db6f 100644 --- a/common.h +++ b/common.h @@ -3,10 +3,13 @@ #include #include #include +#include +#include ptls_context_t *get_tlsctx(); struct addrinfo *get_address(const char *host, const char *port); +void enable_gso(); bool send_pending(quicly_context_t *ctx, int fd, quicly_conn_t *conn); void print_escaped(const char *src, size_t len); @@ -38,3 +41,16 @@ static inline int64_t clamp_int64(int64_t val, int64_t min, int64_t max) } return val; } + +static inline uint64_t get_current_pid() +{ + uint64_t pid; + + #ifdef __APPLE__ + pthread_threadid_np(NULL, &pid); + #else + pid = syscall(SYS_gettid); + #endif + + return pid; +} \ No newline at end of file diff --git a/extern/quicly b/extern/quicly index 0bf020a..06cfce2 160000 --- a/extern/quicly +++ b/extern/quicly @@ -1 +1 @@ -Subproject commit 0bf020a84b71741241b4ccd270971e5897c47eb5 +Subproject commit 06cfce22bcd2ae12e458e7ea6e637ff17c429535 diff --git a/main.c b/main.c index 76ef371..0e8d937 100755 --- a/main.c +++ b/main.c @@ -10,15 +10,16 @@ static void usage(const char *cmd) { printf("Usage: %s [options]\n" - "\n" - "Options:\n" - " -p port to listen on/connect to (default 18080)\n" - " -s run as server\n" - " -c target run as client and connect to target server\n" - " -t time (s) run for X seconds (default 10s)\n" - " -e measure time for connection establishment and first byte only\n" - " -h print this help\n" - "\n", + "\n" + "Options:\n" + " -c target run as client and connect to target server\n" + " -e measure time for connection establishment and first byte only\n" + " -g enable UDP generic segmentation offload\n" + " -p port to listen on/connect to (default 18080)\n" + " -s run as server\n" + " -t time (s) run for X seconds (default 10s)\n" + " -h print this help\n" + "\n", cmd); } @@ -30,11 +31,27 @@ int main(int argc, char** argv) int runtime_s = 10; int ch; bool ttfb_only = false; + bool gso = false; - while ((ch = getopt(argc, argv, "p:sc:t:he")) != -1) { + while ((ch = getopt(argc, argv, "c:egp:st:h")) != -1) { switch (ch) { + case 'c': + host = optarg; + break; + case 'e': + ttfb_only = true; + break; + case 'g': + #ifdef __linux__ + gso = true; + printf("using UDP GSO, requires kernel >= 4.18\n"); + #else + fprintf(stderr, "UDP GSO only supported on linux\n"); + exit(1); + #endif + break; case 'p': - port = optarg; + port = (intptr_t)optarg; if(sscanf(optarg, "%u", &port) < 0 || port > 65535) { fprintf(stderr, "invalid argument passed to -p\n"); exit(1); @@ -43,18 +60,12 @@ int main(int argc, char** argv) case 's': server_mode = true; break; - case 'c': - host = optarg; - break; case 't': if(sscanf(optarg, "%u", &runtime_s) != 1 || runtime_s < 1) { fprintf(stderr, "invalid argument passed to -t\n"); exit(1); } break; - case 'e': - ttfb_only = true; - break; default: usage(argv[0]); exit(1); @@ -74,6 +85,6 @@ int main(int argc, char** argv) char port_char[16]; sprintf(port_char, "%d", port); return server_mode ? - run_server(port_char, "server.crt", "server.key") : - run_client(port_char, host, runtime_s, ttfb_only); -} + run_server(port_char, gso, "server.crt", "server.key") : + run_client(port_char, gso, host, runtime_s, ttfb_only); +} \ No newline at end of file diff --git a/server.c b/server.c index 7e1060b..9a30022 100755 --- a/server.c +++ b/server.c @@ -151,7 +151,7 @@ static void server_read_cb(EV_P_ ev_io *w, int revents) server_send_pending(); } -static void server_on_conn_close(quicly_closed_by_peer_t *self, quicly_conn_t *conn, int err, +static void server_on_conn_close(quicly_closed_by_remote_t *self, quicly_conn_t *conn, int err, uint64_t frame_type, const char *reason, size_t reason_len) { if (QUICLY_ERROR_IS_QUIC_TRANSPORT(err)) { @@ -168,9 +168,9 @@ static void server_on_conn_close(quicly_closed_by_peer_t *self, quicly_conn_t *c } static quicly_stream_open_t stream_open = {&server_on_stream_open}; -static quicly_closed_by_peer_t closed_by_peer = {&server_on_conn_close}; +static quicly_closed_by_remote_t closed_by_remote = {&server_on_conn_close}; -int run_server(const char *port, const char *cert, const char *key) +int run_server(const char *port, bool gso, const char *cert, const char *key) { setup_session_cache(get_tlsctx()); quicly_amend_ptls_context(get_tlsctx()); @@ -178,11 +178,15 @@ int run_server(const char *port, const char *cert, const char *key) server_ctx = quicly_spec_context; server_ctx.tls = get_tlsctx(); server_ctx.stream_open = &stream_open; - server_ctx.closed_by_peer = &closed_by_peer; + server_ctx.closed_by_remote = &closed_by_remote; server_ctx.transport_params.max_stream_data.uni = UINT32_MAX; server_ctx.transport_params.max_stream_data.bidi_local = UINT32_MAX; server_ctx.transport_params.max_stream_data.bidi_remote = UINT32_MAX; + if (gso) { + enable_gso(); + } + load_certificate_chain(server_ctx.tls, cert); load_private_key(server_ctx.tls, key); @@ -201,7 +205,7 @@ int run_server(const char *port, const char *cert, const char *key) return 1; } - printf("starting server on port %s\n", port); + printf("starting server with pid %" PRIu64 " on port %s\n", get_current_pid(), port); ev_io socket_watcher; ev_io_init(&socket_watcher, &server_read_cb, server_socket, EV_READ); diff --git a/server.h b/server.h index 14938a5..cdc237e 100755 --- a/server.h +++ b/server.h @@ -3,5 +3,5 @@ #include #include -int run_server(const char* port, const char *cert, const char *key); +int run_server(const char* port, bool gso, const char *cert, const char *key);