Skip to content

Commit

Permalink
Add GSO support (#5)
Browse files Browse the repository at this point in the history
* Updated gitignore to include build and vscode directories

* Added Port argument, defaults to 18080

* Added usage description for port argument

* Exposed server packet sent/lost counters

* Added exposed server packet counters to README

* Exposed server packet sent/lost counters

* Added exposed server packet counters to README

* Updated quicly to 06cfce22bcd2ae12e458e7ea6e637ff17c429535

* Refactored usage and optargs

* Expose pid on server

* Added GSO support by `-g` cli option

* Refactored README

* Removed incorrect ref to system-wide generic-segmentation-offload

* send_dgrams and send_pending now return bool to signal errors, allowing proper error handling

* Aligned case and switch statements

* Wrapped process id in static inline get_current_pid() function

* Removed unneeded sprintf to cast uint64_t to char

* Moved #ifndef UDP_SEGMENT to the top, added comments detailing linux-only

* Moved #ifndef UDP_SEGMENT down again to include send_dgrams_gso in #ifdef __linux__
  • Loading branch information
kosekmi authored Jun 7, 2020
1 parent 8c55923 commit 4145118
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 52 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 8 additions & 4 deletions client.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -117,21 +117,25 @@ 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;

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());

Expand Down
3 changes: 1 addition & 2 deletions client.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
#include <stdbool.h>
#include <stdint.h>

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();
98 changes: 82 additions & 16 deletions common.c
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#include "common.h"

#include <sys/socket.h>
#include <netinet/udp.h>
#include <netdb.h>
#include <memory.h>
#include <picotls/openssl.h>
#include <errno.h>


ptls_context_t *get_tlsctx()
{
static ptls_context_t tlsctx = {.random_bytes = ptls_openssl_random_bytes,
Expand Down Expand Up @@ -35,40 +35,106 @@ 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);
} else {
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) {
Expand Down
16 changes: 16 additions & 0 deletions common.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
#include <quicly.h>
#include <stdbool.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/syscall.h>

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);

Expand Down Expand Up @@ -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;
}
51 changes: 31 additions & 20 deletions main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
}
14 changes: 9 additions & 5 deletions server.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -168,21 +168,25 @@ 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());

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);

Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion server.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
#include <quicly.h>
#include <stdbool.h>

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);

0 comments on commit 4145118

Please sign in to comment.