qc->state >= QUIC_HS_ST_COMPLETE) {
quic_build_post_handshake_frames(qc);
qel_register_send(&send_list, qc->ael, &qc->ael->pktns->tx.frms);
- qc_send(qc, 0, &send_list);
+ qc_send(qc, 0, &send_list, 0);
}
TRACE_STATE("preparing data (from MUX)", QUIC_EV_CONN_TXPKT, qc);
qel_register_send(&send_list, qc->ael, frms);
- ret = qc_send(qc, 0, &send_list);
+ ret = qc_send(qc, 0, &send_list, 0);
TRACE_LEAVE(QUIC_EV_CONN_TXPKT, qc);
return ret;
}
}
-/* Prepare as much as possible QUIC datagrams/packets for sending from <qels>
- * list of encryption levels. Several packets can be coalesced into a single
+/* Prepare one or several QUIC datagrams/packets for sending from <qels> list
+ * of encryption levels. Several packets can be coalesced into a single
* datagram. The result is written into <buf>.
*
+ * If <max_dgrams> is non null, it limits the number of prepared datagrams.
+ * Useful to support pacing emission.
+ *
* Each datagram is prepended by a two fields header : the datagram length and
* the address of first packet in the datagram.
*
- * Returns the number of bytes prepared in datragrams/packets if succeeded
- * (may be 0), or -1 if something wrong happened.
+ * Returns the number of prepared datagrams on success which may be 0. On error
+ * a negative error code is returned.
*/
static int qc_prep_pkts(struct quic_conn *qc, struct buffer *buf,
- struct list *qels)
+ struct list *qels, int max_dgrams)
{
int ret, cc, padding;
struct quic_tx_packet *first_pkt, *prv_pkt;
uint16_t dglen;
size_t total;
struct quic_enc_level *qel, *tmp_qel;
- uchar gso_dgram_cnt = 0;
+ uchar dgram_cnt = 0, gso_dgram_cnt = 0;
TRACE_ENTER(QUIC_EV_CONN_IO_CB, qc);
/* Currently qc_prep_pkts() does not handle buffer wrapping so the
TRACE_PROTO("TX prep pkts", QUIC_EV_CONN_PHPKTS, qc, qel);
+ /* Start to decrement <max_dgrams> after the first packet built. */
+ if (!dglen && pos != (unsigned char *)b_head(buf)) {
+ if (max_dgrams && !--max_dgrams) {
+ BUG_ON(LIST_ISEMPTY(frms));
+ TRACE_PROTO("reached max allowed built datagrams", QUIC_EV_CONN_PHPKTS, qc, qel);
+ goto out;
+ }
+ }
+
if (!first_pkt)
pos += QUIC_DGRAM_HEADLEN;
* except if it is an too short Initial.
*/
if (first_pkt && (first_pkt->type != QUIC_PACKET_TYPE_INITIAL ||
- wrlen >= QUIC_INITIAL_PACKET_MINLEN))
+ wrlen >= QUIC_INITIAL_PACKET_MINLEN)) {
qc_txb_store(buf, wrlen, first_pkt);
+ ++dgram_cnt;
+ }
TRACE_PROTO("could not prepare anymore packet", QUIC_EV_CONN_PHPKTS, qc, qel);
break;
prv_pkt = cur_pkt;
dglen = 0;
+ ++dgram_cnt;
+
/* man 7 udp UDP_SEGMENT
* The segment size must be chosen such that at
* most 64 datagrams are sent in a single call
wrlen = dglen = 0;
padding = 0;
prv_pkt = NULL;
+ ++dgram_cnt;
gso_dgram_cnt = 0;
}
}
out:
- if (first_pkt)
+ if (first_pkt) {
qc_txb_store(buf, wrlen, first_pkt);
+ ++dgram_cnt;
+ }
if (cc && total) {
BUG_ON(buf != &qc->tx.cc_buf);
qc->tx.cc_dgram_len = dglen;
}
- ret = total;
+ ret = dgram_cnt;
leave:
TRACE_LEAVE(QUIC_EV_CONN_PHPKTS, qc);
return ret;
* specified via quic_enc_level <send_list> through their send_frms member. Set
* <old_data> when reemitted duplicated data.
*
-* Returns 1 on success else 0. Note that <send_list> will always be reset
-* after qc_send() exit.
+ * If <max_dgrams> is non null, it limits the number of emitted datagrams.
+ * Useful to support pacing emission.
+ *
+ * Note that <send_list> will always be emptied on function completion, both on
+ * success and error.
+ *
+ * Returns the number of sent datagrams on success. It means either that all
+ * input frames were sent or emission is interrupted due to pacing. Else a
+ * negative error code is returned.
*/
-int qc_send(struct quic_conn *qc, int old_data, struct list *send_list)
+int qc_send(struct quic_conn *qc, int old_data, struct list *send_list,
+ int max_dgrams)
{
struct quic_enc_level *qel, *tmp_qel;
- int ret = 0, status = 0;
+ int prep_pkts = 0, ret = -1;
struct buffer *buf;
TRACE_ENTER(QUIC_EV_CONN_TXPKT, qc);
while (!LIST_ISEMPTY(send_list) &&
(!(qc->flags & (QUIC_FL_CONN_CLOSING|QUIC_FL_CONN_DRAINING)) ||
(qc->flags & QUIC_FL_CONN_IMMEDIATE_CLOSE))) {
+
+ if (ret < 0)
+ ret = 0;
+
/* Buffer must always be empty before qc_prep_pkts() usage.
* qc_send_ppkts() ensures it is cleared on success.
*/
BUG_ON_HOT(b_data(buf));
b_reset(buf);
- ret = qc_prep_pkts(qc, buf, send_list);
+ prep_pkts = qc_prep_pkts(qc, buf, send_list, max_dgrams);
if (b_data(buf) && !qc_send_ppkts(buf, qc->xprt_ctx)) {
+ ret = -1;
if (qc->flags & QUIC_FL_CONN_TO_KILL)
qc_txb_release(qc);
goto out;
}
- if (ret <= 0) {
+ if (prep_pkts <= 0) {
TRACE_DEVEL("stopping on qc_prep_pkts() return", QUIC_EV_CONN_TXPKT, qc);
break;
}
+ ret += prep_pkts;
+ if (max_dgrams && ret == max_dgrams && !LIST_ISEMPTY(send_list)) {
+ TRACE_DEVEL("stopping for artificial pacing", QUIC_EV_CONN_TXPKT, qc);
+ break;
+ }
+
if ((qc->flags & QUIC_FL_CONN_DRAINING) &&
!(qc->flags & QUIC_FL_CONN_IMMEDIATE_CLOSE)) {
TRACE_DEVEL("draining connection", QUIC_EV_CONN_TXPKT, qc);
if (ret < 0)
goto out;
- status = 1;
-
out:
if (old_data) {
TRACE_STATE("no more need old data for probing", QUIC_EV_CONN_TXPKT, qc);
qel->send_frms = NULL;
}
- TRACE_DEVEL((status ? "leaving" : "leaving in error"), QUIC_EV_CONN_TXPKT, qc);
- return status;
+ TRACE_DEVEL((ret > 0 ? "leaving" : "leaving in error"), QUIC_EV_CONN_TXPKT, qc);
+ return ret;
}
/* Insert <qel> into <send_list> in preparation for sending. Set its send
if (qc->hel)
qel_register_send(&send_list, qc->hel, &hfrms);
- sret = qc_send(qc, 1, &send_list);
+ sret = qc_send(qc, 1, &send_list, 0);
qc_free_frm_list(qc, &ifrms);
qc_free_frm_list(qc, &hfrms);
- if (!sret)
+ if (sret < 0)
goto leave;
}
else {
*/
ipktns->tx.pto_probe = 1;
qel_register_send(&send_list, qc->iel, &ifrms);
- sret = qc_send(qc, 0, &send_list);
+ sret = qc_send(qc, 0, &send_list, 0);
qc_free_frm_list(qc, &ifrms);
qc_free_frm_list(qc, &hfrms);
- if (!sret)
+ if (sret < 0)
goto leave;
break;
if (!LIST_ISEMPTY(&frms1)) {
hpktns->tx.pto_probe = 1;
qel_register_send(&send_list, qc->hel, &frms1);
- sret = qc_send(qc, 1, &send_list);
+ sret = qc_send(qc, 1, &send_list, 0);
qc_free_frm_list(qc, &frms1);
- if (!sret)
+ if (sret < 0)
goto leave;
}
}
if (!LIST_ISEMPTY(&frms1)) {
apktns->tx.pto_probe = 1;
qel_register_send(&send_list, qc->ael, &frms1);
- sret = qc_send(qc, 1, &send_list);
+ sret = qc_send(qc, 1, &send_list, 0);
qc_free_frm_list(qc, &frms1);
- if (!sret) {
+ if (sret < 0) {
qc_free_frm_list(qc, &frms2);
goto leave;
}
if (!LIST_ISEMPTY(&frms2)) {
apktns->tx.pto_probe = 1;
qel_register_send(&send_list, qc->ael, &frms2);
- sret = qc_send(qc, 1, &send_list);
+ sret = qc_send(qc, 1, &send_list, 0);
qc_free_frm_list(qc, &frms2);
- if (!sret)
+ if (sret < 0)
goto leave;
}
TRACE_STATE("no more need to probe 01RTT packet number space",