diff --git a/include/quicly.h b/include/quicly.h index dc20e7c7..e8bf132c 100644 --- a/include/quicly.h +++ b/include/quicly.h @@ -1061,11 +1061,12 @@ static int quicly_stream_has_receive_side(int is_client, quicly_stream_id_t stre */ static int quicly_stream_is_self_initiated(quicly_stream_t *stream); /** - * Registers a datagram frame payload to be sent. When the applications calls `quicly_send` the first time after registering the - * datagram frame payload, the payload is either sent or the reference is discarded. Until then, it is the caller's responsibility - * to retain the memory pointed to by `payload`. At the moment, DATAFRAM frames are not congestion controlled. + * Sends QUIC DATAGRAM frames. Some of the frames being provided may get dropped. + * Notes: + * * At the moment, emission of QUIC packets carrying DATAGRAM frames is not congestion controlled. + * * While the API is designed to look like synchronous, application still has to call `quicly_send` for the time being. */ -void quicly_set_datagram_frame(quicly_conn_t *conn, ptls_iovec_t payload); +void quicly_send_datagram_frames(quicly_conn_t *conn, ptls_iovec_t *datagrams, size_t num_datagrams); /** * */ diff --git a/lib/quicly.c b/lib/quicly.c index e290c052..8890adcf 100644 --- a/lib/quicly.c +++ b/lib/quicly.c @@ -337,9 +337,12 @@ struct st_quicly_conn_t { */ quicly_retire_cid_set_t retire_cid; /** - * DATAGRAM frame payload to be sent + * payload of DATAGRAM frames to be sent */ - ptls_iovec_t datagram_frame_payload; + struct { + ptls_iovec_t payloads[10]; + size_t count; + } datagram_frame_payloads; } egress; /** * crypto data @@ -552,6 +555,15 @@ static void dispose_cipher(struct st_quicly_cipher_context_t *ctx) ptls_cipher_free(ctx->header_protection); } +static void clear_datagram_frame_payloads(quicly_conn_t *conn) +{ + for (size_t i = 0; i != conn->egress.datagram_frame_payloads.count; ++i) { + free(conn->egress.datagram_frame_payloads.payloads[i].base); + conn->egress.datagram_frame_payloads.payloads[i] = ptls_iovec_init(NULL, 0); + } + conn->egress.datagram_frame_payloads.count = 0; +} + static int is_retry(quicly_conn_t *conn) { return conn->retry_scid.len != UINT8_MAX; @@ -1502,6 +1514,7 @@ void quicly_free(quicly_conn_t *conn) update_open_count(conn->super.ctx, -1); destroy_all_streams(conn, 0, 1); + clear_datagram_frame_payloads(conn); quicly_maxsender_dispose(&conn->ingress.max_data.sender); quicly_maxsender_dispose(&conn->ingress.max_streams.uni); @@ -2767,14 +2780,12 @@ static int on_ack_retire_connection_id(quicly_sentmap_t *map, const quicly_sent_ static int should_send_datagram_frame(quicly_conn_t *conn) { - if (conn->egress.datagram_frame_payload.base == NULL) + if (conn->egress.datagram_frame_payloads.count == 0) return 0; if (conn->application == NULL) return 0; if (conn->application->cipher.egress.key.aead == NULL) return 0; - if (conn->super.remote.transport_params.max_datagram_frame_size < conn->egress.datagram_frame_payload.len) - return 0; return 1; } @@ -4204,15 +4215,19 @@ static int do_send(quicly_conn_t *conn, quicly_send_context_t *s) * This is because we do not have a way to retract the generation of a QUIC packet. * * Does not notify the application that the frame was dropped internally. */ if (should_send_datagram_frame(conn)) { - size_t required_space = quicly_datagram_frame_capacity(conn->egress.datagram_frame_payload); - if ((ret = _do_allocate_frame(conn, s, required_space, 1)) != 0) - goto Exit; - if (s->dst_end - s->dst >= required_space) { - s->dst = quicly_encode_datagram_frame(s->dst, conn->egress.datagram_frame_payload); - QUICLY_PROBE(DATAGRAM_SEND, conn, conn->stash.now, conn->egress.datagram_frame_payload.base, - conn->egress.datagram_frame_payload.len); - conn->egress.datagram_frame_payload = ptls_iovec_init(NULL, 0); - ++conn->super.stats.num_frames_sent.datagram; + for (size_t i = 0; i != conn->egress.datagram_frame_payloads.count; ++i) { + ptls_iovec_t *payload = conn->egress.datagram_frame_payloads.payloads + i; + size_t required_space = quicly_datagram_frame_capacity(*payload); + if ((ret = _do_allocate_frame(conn, s, required_space, 1)) != 0) + goto Exit; + if (s->dst_end - s->dst >= required_space) { + s->dst = quicly_encode_datagram_frame(s->dst, *payload); + QUICLY_PROBE(DATAGRAM_SEND, conn, conn->stash.now, payload->base, payload->len); + } else { + /* FIXME: At the moment, we add a padding because we do not have a way to reclaim allocated space, and because + * it is forbidden to send an empty QUIC packet. */ + *s->dst++ = QUICLY_FRAME_TYPE_PADDING; + } } } if (!ack_only) { @@ -4343,9 +4358,18 @@ static int do_send(quicly_conn_t *conn, quicly_send_context_t *s) return ret; } -void quicly_set_datagram_frame(quicly_conn_t *conn, ptls_iovec_t payload) +void quicly_send_datagram_frames(quicly_conn_t *conn, ptls_iovec_t *datagrams, size_t num_datagrams) { - conn->egress.datagram_frame_payload = payload; + for (size_t i = 0; i != num_datagrams; ++i) { + if (conn->egress.datagram_frame_payloads.count == PTLS_ELEMENTSOF(conn->egress.datagram_frame_payloads.payloads)) + break; + void *copied; + if ((copied = malloc(datagrams[i].len)) == NULL) + break; + memcpy(copied, datagrams[i].base, datagrams[i].len); + conn->egress.datagram_frame_payloads.payloads[conn->egress.datagram_frame_payloads.count++] = + ptls_iovec_init(copied, datagrams[i].len); + } } int quicly_send(quicly_conn_t *conn, quicly_address_t *dest, quicly_address_t *src, struct iovec *datagrams, size_t *num_datagrams, @@ -4403,7 +4427,7 @@ int quicly_send(quicly_conn_t *conn, quicly_address_t *dest, quicly_address_t *s assert_consistency(conn, 1); Exit: - conn->egress.datagram_frame_payload = ptls_iovec_init(NULL, 0); + clear_datagram_frame_payloads(conn); if (s.num_datagrams != 0) { *dest = conn->super.remote.address; *src = conn->super.local.address; diff --git a/src/cli.c b/src/cli.c index 89e30d35..39e2e161 100644 --- a/src/cli.c +++ b/src/cli.c @@ -45,7 +45,6 @@ FILE *quicly_trace_fp = NULL; static unsigned verbosity = 0; static int suppress_output = 0, send_datagram_frame = 0; static int64_t enqueue_requests_at = 0, request_interval = 0; -static void *datagram_frame_payload_buf; static void hexdump(const char *title, const uint8_t *p, size_t l) { @@ -489,34 +488,15 @@ static int send_pending(int fd, quicly_conn_t *conn) if ((ret = quicly_send(conn, &dest, &src, packets, &num_packets, buf, sizeof(buf))) == 0 && num_packets != 0) send_packets(fd, &dest.sa, packets, num_packets); - if (datagram_frame_payload_buf != NULL) { - free(datagram_frame_payload_buf); - datagram_frame_payload_buf = NULL; - } - return ret; } -static void set_datagram_frame(quicly_conn_t *conn, ptls_iovec_t payload) -{ - if (datagram_frame_payload_buf != NULL) - free(datagram_frame_payload_buf); - - /* replace payload.base with an allocated buffer */ - datagram_frame_payload_buf = malloc(payload.len); - memcpy(datagram_frame_payload_buf, payload.base, payload.len); - payload.base = datagram_frame_payload_buf; - - /* set data to be sent. The buffer is being freed in `send_pending` after `quicly_send` is being called. */ - quicly_set_datagram_frame(conn, payload); -} - static void on_receive_datagram_frame(quicly_receive_datagram_frame_t *self, quicly_conn_t *conn, ptls_iovec_t payload) { printf("DATAGRAM: %.*s\n", (int)payload.len, payload.base); /* send responds with a datagram frame */ if (!quicly_is_client(conn)) - set_datagram_frame(conn, payload); + quicly_send_datagram_frames(conn, &payload, 1); } static void enqueue_requests(quicly_conn_t *conn) @@ -618,7 +598,8 @@ static int run_client(int fd, struct sockaddr *sa, const char *host) quicly_receive(conn, NULL, &sa, &packet); if (send_datagram_frame && quicly_connection_is_ready(conn)) { const char *message = "hello datagram!"; - set_datagram_frame(conn, ptls_iovec_init(message, strlen(message))); + ptls_iovec_t datagram = ptls_iovec_init(message, strlen(message)); + quicly_send_datagram_frames(conn, &datagram, 1); send_datagram_frame = 0; } }