# QUIC support
AC_ARG_ENABLE([quic],
- AS_HELP_STRING([--enable-quic=auto|yes|no], [Support DoQ (needs libngtcp2 = 0.7.0, gnutls >= 3.7.2) [default=auto]]),
+ AS_HELP_STRING([--enable-quic=auto|yes|no], [Support DoQ (needs libngtcp2 = 0.8.0, gnutls >= 3.7.2) [default=auto]]),
[], [enable_quic=auto])
AS_CASE([$enable_quic],
- [auto], [PKG_CHECK_MODULES([libngtcp2], [libngtcp2 = 0.7.0 libngtcp2_crypto_gnutls], [enable_quic=yes], [enable_quic=no])],
- [yes], [PKG_CHECK_MODULES([libngtcp2], [libngtcp2 = 0.7.0 libngtcp2_crypto_gnutls], [enable_quic=yes],
+ [auto], [PKG_CHECK_MODULES([libngtcp2], [libngtcp2 = 0.8.0 libngtcp2_crypto_gnutls], [enable_quic=yes], [enable_quic=no])],
+ [yes], [PKG_CHECK_MODULES([libngtcp2], [libngtcp2 = 0.8.0 libngtcp2_crypto_gnutls], [enable_quic=yes],
AS_IF([test "$gnutls_quic" = "yes"],
[enable_quic=embedded
embedded_libngtcp2_CFLAGS="-I\$(top_srcdir)/src/contrib/libngtcp2 -I\$(top_srcdir)/src/contrib/libngtcp2/ngtcp2/lib"
DNS-over-QUIC (DoQ) support in :doc:`knotd<man_knotd>`, :doc:`kxdpgun<man_kxdpgun>`,
and :doc:`kdig<man_kdig>`:
-* libngtcp2 == 0.7.0 (or embedded one via `--enable-quic`)
+* libngtcp2 == 0.8.0 (or embedded one via `--enable-quic`)
* gnutls >= 3.7.2
* :ref:`Mode XDP` (for knotd and kxdpgun)
if (((rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) || padded) &&
(!path || ngtcp2_path_eq(&conn->dcid.current.ps.path, path))) {
- if (pi) {
+ if (pi && !(rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE)) {
conn_handle_tx_ecn(conn, pi, &rtb_entry_flags, pktns, &hd, ts);
}
nwrite);
}
}
- } else if (pi && conn->tx.ecn.state == NGTCP2_ECN_STATE_CAPABLE) {
+ } else if (pi && !(rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) &&
+ conn->tx.ecn.state == NGTCP2_ECN_STATE_CAPABLE) {
conn_handle_tx_ecn(conn, pi, NULL, pktns, &hd, ts);
}
return 0;
}
+int ngtcp2_conn_start_pmtud(ngtcp2_conn *conn) {
+ return conn_start_pmtud(conn);
+}
+
void ngtcp2_conn_stop_pmtud(ngtcp2_conn *conn) {
if (!conn->pmtud) {
return;
/* Stop calling callback if application has called
ngtcp2_conn_shutdown_stream_read() inside the callback.
Because it doubly counts connection window. */
- if (strm->flags & (NGTCP2_STRM_FLAG_STOP_SENDING)) {
+ if (strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING) {
return 0;
}
ngtcp2_conn *conn, const ngtcp2_transport_params *params) {
int rv;
- assert(!(conn->flags & NGTCP2_CONN_FLAG_TRANSPORT_PARAM_RECVED));
+ /* We expect this function is called once per QUIC connection, but
+ GnuTLS server seems to call TLS extension callback twice if it
+ sends HelloRetryRequest. In practice, same QUIC transport
+ parameters are sent in the 2nd client flight, just returning 0
+ would cause no harm. */
+ if (conn->flags & NGTCP2_CONN_FLAG_TRANSPORT_PARAM_RECVED) {
+ return 0;
+ }
/* Assume that ngtcp2_decode_transport_params sets default value if
active_connection_id_limit is omitted. */
conn->negotiated_version;
ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON,
- "the negotiated version is %08x", conn->negotiated_version);
+ "the negotiated version is 0x%08x",
+ conn->negotiated_version);
} else {
rv = conn_client_validate_transport_params(conn, params);
if (rv != 0) {
*/
static int conn_shutdown_stream_write(ngtcp2_conn *conn, ngtcp2_strm *strm,
uint64_t app_error_code) {
+ ngtcp2_strm_set_app_error_code(strm, app_error_code);
+
if ((strm->flags & NGTCP2_STRM_FLAG_SENT_RST) ||
ngtcp2_strm_is_all_tx_data_fin_acked(strm)) {
return 0;
/* Set this flag so that we don't accidentally send DATA to this
stream. */
strm->flags |= NGTCP2_STRM_FLAG_SHUT_WR | NGTCP2_STRM_FLAG_SENT_RST;
- ngtcp2_strm_set_app_error_code(strm, app_error_code);
ngtcp2_strm_streamfrq_clear(strm);
strm = ngtcp2_conn_find_stream(conn, stream_id);
if (strm == NULL) {
- return NGTCP2_ERR_STREAM_NOT_FOUND;
+ return 0;
}
rv = conn_shutdown_stream_read(conn, strm, app_error_code);
strm = ngtcp2_conn_find_stream(conn, stream_id);
if (strm == NULL) {
- return NGTCP2_ERR_STREAM_NOT_FOUND;
+ return 0;
}
return conn_shutdown_stream_write(conn, strm, app_error_code);
strm = ngtcp2_conn_find_stream(conn, stream_id);
if (strm == NULL) {
- return NGTCP2_ERR_STREAM_NOT_FOUND;
+ return 0;
}
return conn_shutdown_stream_read(conn, strm, app_error_code);
strm = ngtcp2_conn_find_stream(conn, stream_id);
if (strm == NULL) {
- return NGTCP2_ERR_STREAM_NOT_FOUND;
+ return 0;
}
return conn_extend_max_stream_offset(conn, strm, datalen);
&conn->log, NGTCP2_LOG_EVENT_RCV,
"ignore rtt sample because ack_delay is too large latest_rtt=%" PRIu64
" min_rtt=%" PRIu64 " ack_delay=%" PRIu64,
- (uint64_t)(rtt / NGTCP2_MILLISECONDS),
- (uint64_t)(cstat->min_rtt / NGTCP2_MILLISECONDS),
- (uint64_t)(ack_delay / NGTCP2_MILLISECONDS));
+ rtt / NGTCP2_MILLISECONDS, cstat->min_rtt / NGTCP2_MILLISECONDS,
+ ack_delay / NGTCP2_MILLISECONDS);
return NGTCP2_ERR_INVALID_ARGUMENT;
}
cstat->smoothed_rtt = (cstat->smoothed_rtt * 7 + rtt) / 8;
}
- ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_RCV,
- "latest_rtt=%" PRIu64 " min_rtt=%" PRIu64
- " smoothed_rtt=%" PRIu64 " rttvar=%" PRIu64
- " ack_delay=%" PRIu64,
- (uint64_t)(cstat->latest_rtt / NGTCP2_MILLISECONDS),
- (uint64_t)(cstat->min_rtt / NGTCP2_MILLISECONDS),
- cstat->smoothed_rtt / NGTCP2_MILLISECONDS,
- cstat->rttvar / NGTCP2_MILLISECONDS,
- (uint64_t)(ack_delay / NGTCP2_MILLISECONDS));
+ ngtcp2_log_info(
+ &conn->log, NGTCP2_LOG_EVENT_RCV,
+ "latest_rtt=%" PRIu64 " min_rtt=%" PRIu64 " smoothed_rtt=%" PRIu64
+ " rttvar=%" PRIu64 " ack_delay=%" PRIu64,
+ cstat->latest_rtt / NGTCP2_MILLISECONDS,
+ cstat->min_rtt / NGTCP2_MILLISECONDS,
+ cstat->smoothed_rtt / NGTCP2_MILLISECONDS,
+ cstat->rttvar / NGTCP2_MILLISECONDS, ack_delay / NGTCP2_MILLISECONDS);
return 0;
}
*cstat = conn->cstat;
}
-static ngtcp2_pktns *conn_get_earliest_pktns(ngtcp2_conn *conn,
- ngtcp2_tstamp *pts,
- const ngtcp2_tstamp *times) {
- ngtcp2_pktns *ns[] = {conn->in_pktns, conn->hs_pktns, &conn->pktns};
- ngtcp2_pktns *res = NULL;
+static void conn_get_loss_time_and_pktns(ngtcp2_conn *conn,
+ ngtcp2_tstamp *ploss_time,
+ ngtcp2_pktns **ppktns) {
+ ngtcp2_pktns *const ns[] = {conn->hs_pktns, &conn->pktns};
+ ngtcp2_conn_stat *cstat = &conn->cstat;
+ ngtcp2_duration *loss_time = cstat->loss_time;
+ ngtcp2_tstamp earliest_loss_time = loss_time[NGTCP2_PKTNS_ID_INITIAL];
+ ngtcp2_pktns *pktns = conn->in_pktns;
size_t i;
- ngtcp2_tstamp earliest_ts = UINT64_MAX;
- for (i = NGTCP2_PKTNS_ID_INITIAL; i < NGTCP2_PKTNS_ID_MAX; ++i) {
+ for (i = 0; i < sizeof(ns) / sizeof(ns[0]); ++i) {
if (ns[i] == NULL || ns[i]->rtb.num_pto_eliciting == 0 ||
- (times[i] == UINT64_MAX ||
- (earliest_ts != UINT64_MAX && times[i] >= earliest_ts) ||
- (i == NGTCP2_PKTNS_ID_APPLICATION &&
- !(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED)))) {
+ loss_time[i] >= earliest_loss_time) {
continue;
}
- earliest_ts = times[i];
- res = ns[i];
+ earliest_loss_time = loss_time[i];
+ pktns = ns[i];
}
- if (res == NULL && !conn->server) {
- if (conn->hs_pktns && conn->hs_pktns->crypto.tx.ckm) {
- res = conn->hs_pktns;
- } else {
- res = conn->in_pktns;
- }
+ if (ploss_time) {
+ *ploss_time = earliest_loss_time;
}
-
- if (pts) {
- *pts = earliest_ts;
+ if (ppktns) {
+ *ppktns = pktns;
}
- return res;
}
static ngtcp2_tstamp conn_get_earliest_pto_expiry(ngtcp2_conn *conn,
- const ngtcp2_tstamp *times,
- size_t pto_count,
ngtcp2_tstamp ts) {
ngtcp2_pktns *ns[] = {conn->in_pktns, conn->hs_pktns, &conn->pktns};
size_t i;
ngtcp2_tstamp earliest_ts = UINT64_MAX, t;
ngtcp2_conn_stat *cstat = &conn->cstat;
+ ngtcp2_tstamp *times = cstat->last_tx_pkt_ts;
ngtcp2_duration duration =
compute_pto(cstat->smoothed_rtt, cstat->rttvar, /* max_ack_delay = */ 0) *
- (1ULL << pto_count);
+ (1ULL << cstat->pto_count);
for (i = NGTCP2_PKTNS_ID_INITIAL; i < NGTCP2_PKTNS_ID_MAX; ++i) {
if (ns[i] == NULL || ns[i]->rtb.num_pto_eliciting == 0 ||
if (i == NGTCP2_PKTNS_ID_APPLICATION) {
assert(conn->remote.transport_params);
- t += conn->remote.transport_params->max_ack_delay * (1ULL << pto_count);
+ t += conn->remote.transport_params->max_ack_delay *
+ (1ULL << cstat->pto_count);
}
if (t < earliest_ts) {
ngtcp2_pktns *pktns = &conn->pktns;
ngtcp2_tstamp earliest_loss_time;
- conn_get_earliest_pktns(conn, &earliest_loss_time, cstat->loss_time);
+ conn_get_loss_time_and_pktns(conn, &earliest_loss_time, NULL);
if (earliest_loss_time != UINT64_MAX) {
cstat->loss_detection_timer = earliest_loss_time;
return;
}
- cstat->loss_detection_timer = conn_get_earliest_pto_expiry(
- conn, cstat->last_tx_pkt_ts, cstat->pto_count, ts);
+ cstat->loss_detection_timer = conn_get_earliest_pto_expiry(conn, ts);
timeout =
cstat->loss_detection_timer > ts ? cstat->loss_detection_timer - ts : 0;
ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_RCV,
"loss_detection_timer=%" PRIu64 " timeout=%" PRIu64,
- cstat->loss_detection_timer,
- (uint64_t)(timeout / NGTCP2_MILLISECONDS));
+ cstat->loss_detection_timer, timeout / NGTCP2_MILLISECONDS);
}
int ngtcp2_conn_on_loss_detection_timer(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
ngtcp2_pktns *in_pktns = conn->in_pktns;
ngtcp2_pktns *hs_pktns = conn->hs_pktns;
ngtcp2_tstamp earliest_loss_time;
- ngtcp2_pktns *loss_pktns =
- conn_get_earliest_pktns(conn, &earliest_loss_time, cstat->loss_time);
+ ngtcp2_pktns *loss_pktns = NULL;
conn->log.last_ts = ts;
conn->qlog.last_ts = ts;
return 0;
}
+ conn_get_loss_time_and_pktns(conn, &earliest_loss_time, &loss_pktns);
+
ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_RCV,
"loss detection timer fired");
if (earliest_loss_time != UINT64_MAX) {
+ assert(loss_pktns);
+
rv = ngtcp2_conn_detect_lost_pkt(conn, loss_pktns, cstat, ts);
if (rv != 0) {
return rv;
}
}
+size_t ngtcp2_conn_get_stream_loss_count(ngtcp2_conn *conn, int64_t stream_id) {
+ ngtcp2_strm *strm = ngtcp2_conn_find_stream(conn, stream_id);
+
+ if (strm == NULL) {
+ return 0;
+ }
+
+ return strm->tx.loss_count;
+}
+
void ngtcp2_path_challenge_entry_init(ngtcp2_path_challenge_entry *pcent,
const ngtcp2_path *path,
const uint8_t *data) {
size_t destlen, uint64_t app_error_code, const uint8_t *reason,
size_t reasonlen, ngtcp2_tstamp ts);
+int ngtcp2_conn_start_pmtud(ngtcp2_conn *conn);
+
void ngtcp2_conn_stop_pmtud(ngtcp2_conn *conn);
/**
ngtcp2_mem_free(mem, pc);
}
-int ngtcp2_pkt_decode_version_cid(uint32_t *pversion, const uint8_t **pdcid,
- size_t *pdcidlen, const uint8_t **pscid,
- size_t *pscidlen, const uint8_t *data,
+int ngtcp2_pkt_decode_version_cid(ngtcp2_version_cid *dest, const uint8_t *data,
size_t datalen, size_t short_dcidlen) {
size_t len;
uint32_t version;
return NGTCP2_ERR_INVALID_ARGUMENT;
}
- *pversion = version;
- *pdcid = &data[6];
- *pdcidlen = dcidlen;
- *pscid = &data[6 + dcidlen + 1];
- *pscidlen = scidlen;
+ dest->version = version;
+ dest->dcid = &data[6];
+ dest->dcidlen = dcidlen;
+ dest->scid = &data[6 + dcidlen + 1];
+ dest->scidlen = scidlen;
if (!version) {
/* VN */
return NGTCP2_ERR_INVALID_ARGUMENT;
}
- *pversion = 0;
- *pdcid = &data[1];
- *pdcidlen = short_dcidlen;
- *pscid = NULL;
- *pscidlen = 0;
+ dest->version = 0;
+ dest->dcid = &data[1];
+ dest->dcidlen = short_dcidlen;
+ dest->scid = NULL;
+ dest->scidlen = 0;
return 0;
}
/* Retry packet is sent at most once per one connection attempt. In
the first connection attempt, client has to send random DCID
- which is at least 8 bytes long. */
- if (odcid->datalen < 8) {
+ which is at least NGTCP2_MIN_INITIAL_DCIDLEN bytes long. */
+ if (odcid->datalen < NGTCP2_MIN_INITIAL_DCIDLEN) {
return NGTCP2_ERR_INVALID_ARGUMENT;
}
rtb->cc_bytes_in_flight = 0;
rtb->persistent_congestion_start_ts = UINT64_MAX;
rtb->num_lost_pkts = 0;
+ rtb->num_lost_pmtud_pkts = 0;
}
void ngtcp2_rtb_free(ngtcp2_rtb *rtb) {
}
}
-static void rtb_on_remove(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent,
- ngtcp2_conn_stat *cstat) {
+static size_t rtb_on_remove(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent,
+ ngtcp2_conn_stat *cstat) {
if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED) {
assert(rtb->num_lost_pkts);
--rtb->num_lost_pkts;
- return;
+
+ if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) {
+ assert(rtb->num_lost_pmtud_pkts);
+ --rtb->num_lost_pmtud_pkts;
+ }
+
+ return 0;
}
if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) {
assert(rtb->cc_bytes_in_flight >= ent->pktlen);
rtb->cc_bytes_in_flight -= ent->pktlen;
+
+ /* If PMTUD packet is lost, we do not report the lost bytes to the
+ caller in order to ignore loss of PMTUD packet. */
+ if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) {
+ return 0;
+ }
+
+ return ent->pktlen;
}
+
+ return 0;
}
+/* NGTCP2_RECLAIM_FLAG_NONE indicates that no flag is set. */
+#define NGTCP2_RECLAIM_FLAG_NONE 0x00u
+/* NGTCP2_RECLAIM_FLAG_ON_LOSS indicates that frames are reclaimed
+ because of the packet loss.*/
+#define NGTCP2_RECLAIM_FLAG_ON_LOSS 0x01u
+
/*
* rtb_reclaim_frame queues unacknowledged frames included in |ent|
* for retransmission. The re-queued frames are not deleted from
- * |ent|. It returns the number of frames queued.
+ * |ent|. It returns the number of frames queued. |flags| is bitwise
+ * OR of 0 or more of NGTCP2_RECLAIM_FLAG_*.
*/
-static ngtcp2_ssize rtb_reclaim_frame(ngtcp2_rtb *rtb, ngtcp2_conn *conn,
- ngtcp2_pktns *pktns,
+static ngtcp2_ssize rtb_reclaim_frame(ngtcp2_rtb *rtb, uint8_t flags,
+ ngtcp2_conn *conn, ngtcp2_pktns *pktns,
ngtcp2_rtb_entry *ent) {
ngtcp2_frame_chain *frc, *nfrc, **pfrc = &pktns->tx.frq;
ngtcp2_frame *fr;
}
}
+ if ((flags & NGTCP2_RECLAIM_FLAG_ON_LOSS) &&
+ ent->hd.pkt_num != strm->tx.last_lost_pkt_num) {
+ strm->tx.last_lost_pkt_num = ent->hd.pkt_num;
+ ++strm->tx.loss_count;
+ }
+
rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new(
&nfrc, fr->stream.datacnt, rtb->frc_objalloc, rtb->mem);
if (rv != 0) {
ngtcp2_qlog_pkt_lost(rtb->qlog, ent);
}
- if (rtb->cc->on_pkt_lost) {
+ if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) {
+ ++rtb->num_lost_pmtud_pkts;
+ } else if (rtb->cc->on_pkt_lost) {
cc->on_pkt_lost(cc, cstat,
ngtcp2_cc_pkt_init(&pkt, ent->hd.pkt_num, ent->pktlen,
rtb->pktns_id, ent->ts, ent->rst.lost,
assert(!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED));
assert(UINT64_MAX == ent->lost_ts);
- reclaimed = rtb_reclaim_frame(rtb, conn, pktns, ent);
+ reclaimed =
+ rtb_reclaim_frame(rtb, NGTCP2_RECLAIM_FLAG_ON_LOSS, conn, pktns, ent);
if (reclaimed < 0) {
return (int)reclaimed;
}
size_t ecn_acked = 0;
int verify_ecn = 0;
ngtcp2_cc_ack cc_ack = {0};
- size_t num_lost_pkts = rtb->num_lost_pkts;
+ size_t num_lost_pkts = rtb->num_lost_pkts - rtb->num_lost_pmtud_pkts;
cc_ack.prior_bytes_in_flight = cstat->bytes_in_flight;
cc_ack.rtt = UINT64_MAX;
}
if (rtb->cc->on_spurious_congestion && num_lost_pkts &&
- rtb->num_lost_pkts == 0) {
+ rtb->num_lost_pkts - rtb->num_lost_pmtud_pkts == 0) {
rtb->cc->on_spurious_congestion(cc, cstat, ts);
}
}
static int rtb_pkt_lost(ngtcp2_rtb *rtb, ngtcp2_conn_stat *cstat,
- const ngtcp2_rtb_entry *ent, uint64_t loss_delay,
- ngtcp2_tstamp lost_send_time, size_t pkt_thres) {
+ const ngtcp2_rtb_entry *ent, ngtcp2_duration loss_delay,
+ size_t pkt_thres, ngtcp2_tstamp ts) {
ngtcp2_tstamp loss_time;
- if (ent->ts <= lost_send_time ||
+ if (ent->ts + loss_delay <= ts ||
rtb->largest_acked_tx_pkt_num >= ent->hd.pkt_num + (int64_t)pkt_thres) {
return 1;
}
ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts) {
ngtcp2_rtb_entry *ent;
ngtcp2_duration loss_delay;
- ngtcp2_tstamp lost_send_time;
ngtcp2_ksl_it it;
ngtcp2_tstamp latest_ts, oldest_ts;
int64_t last_lost_pkt_num;
size_t ecn_pkt_lost = 0;
ngtcp2_tstamp start_ts;
ngtcp2_duration pto = ngtcp2_conn_compute_pto(conn, pktns);
- uint64_t prior_bytes_in_flight = cstat->bytes_in_flight;
+ uint64_t bytes_lost = 0;
ngtcp2_duration max_ack_delay;
pkt_thres = ngtcp2_max(pkt_thres, NGTCP2_PKT_THRESHOLD);
pkt_thres = ngtcp2_min(pkt_thres, 256);
cstat->loss_time[rtb->pktns_id] = UINT64_MAX;
loss_delay = compute_pkt_loss_delay(cstat);
- lost_send_time = ts - loss_delay;
it = ngtcp2_ksl_lower_bound(&rtb->ents, &rtb->largest_acked_tx_pkt_num);
for (; !ngtcp2_ksl_it_end(&it); ngtcp2_ksl_it_next(&it)) {
break;
}
- if (rtb_pkt_lost(rtb, cstat, ent, loss_delay, lost_send_time,
- (size_t)pkt_thres)) {
+ if (rtb_pkt_lost(rtb, cstat, ent, loss_delay, (size_t)pkt_thres, ts)) {
/* All entries from ent are considered to be lost. */
latest_ts = oldest_ts = ent->ts;
last_lost_pkt_num = ent->hd.pkt_num;
++ecn_pkt_lost;
}
- rtb_on_remove(rtb, ent, cstat);
+ bytes_lost += rtb_on_remove(rtb, ent, cstat);
rv = rtb_on_pkt_lost(rtb, &it, ent, cstat, conn, pktns, ts);
if (rv != 0) {
return rv;
}
}
+ /* If only PMTUD packets are lost, do not trigger congestion
+ event. */
+ if (bytes_lost == 0) {
+ break;
+ }
+
switch (conn->tx.ecn.state) {
case NGTCP2_ECN_STATE_TESTING:
if (conn->tx.ecn.validation_start_ts == UINT64_MAX) {
ngtcp2_rtb_remove_excessive_lost_pkt(rtb, (size_t)pkt_thres);
if (ppkt_lost) {
- assert(prior_bytes_in_flight >= cstat->bytes_in_flight);
-
- *ppkt_lost = prior_bytes_in_flight - cstat->bytes_in_flight;
+ *ppkt_lost = bytes_lost;
}
return 0;
"removing stale lost pkn=%" PRId64, ent->hd.pkt_num);
--rtb->num_lost_pkts;
+
+ if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) {
+ --rtb->num_lost_pmtud_pkts;
+ }
+
rv = ngtcp2_ksl_remove_hint(&rtb->ents, &it, &it, &ent->hd.pkt_num);
assert(0 == rv);
ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc,
"removing stale lost pkn=%" PRId64, ent->hd.pkt_num);
--rtb->num_lost_pkts;
+
+ if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) {
+ --rtb->num_lost_pmtud_pkts;
+ }
+
rv = ngtcp2_ksl_remove_hint(&rtb->ents, &it, &it, &ent->hd.pkt_num);
assert(0 == rv);
ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc,
if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED) {
--rtb->num_lost_pkts;
+ if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) {
+ --rtb->num_lost_pmtud_pkts;
+ }
+
ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_RCV,
"pkn=%" PRId64
" was declared lost and has already been retransmitted",
assert(ent->frc);
- reclaimed = rtb_reclaim_frame(rtb, conn, pktns, ent);
+ reclaimed =
+ rtb_reclaim_frame(rtb, NGTCP2_RECLAIM_FLAG_NONE, conn, pktns, ent);
if (reclaimed < 0) {
return reclaimed;
}
/* num_lost_pkts is the number entries in ents which has
NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED flag set. */
size_t num_lost_pkts;
+ /* num_lost_pmtud_pkts is the number of entries in ents which have
+ both NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED and
+ NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE flags set. */
+ size_t num_lost_pmtud_pkts;
} ngtcp2_rtb;
/*
strm->tx.offset = 0;
strm->tx.max_offset = max_tx_offset;
strm->tx.last_max_stream_data_ts = UINT64_MAX;
+ strm->tx.loss_count = 0;
+ strm->tx.last_lost_pkt_num = -1;
strm->rx.rob = NULL;
strm->rx.cont_offset = 0;
strm->rx.last_offset = 0;
/* last_max_stream_data_ts is the timestamp when last
MAX_STREAM_DATA frame is sent. */
ngtcp2_tstamp last_max_stream_data_ts;
+ /* loss_count is the number of packets that contain STREAM
+ frame for this stream and are declared to be lost. It may
+ include the spurious losses. It does not include a packet
+ whose contents have been reclaimed for PTO and which is
+ later declared to be lost. Those data are not blocked by
+ the flow control and will be sent immediately if no other
+ restrictions are applied. */
+ size_t loss_count;
+ /* last_lost_pkt_num is the packet number of the packet that
+ is counted to loss_count. It is used to avoid to count
+ multiple STREAM frames in one lost packet. */
+ int64_t last_lost_pkt_num;
} tx;
struct {
*
* :macro:`NGTCP2_SECONDS` is a count of tick which corresponds to 1 second.
*/
-#define NGTCP2_SECONDS ((uint64_t)1000000000ULL)
+#define NGTCP2_SECONDS ((ngtcp2_duration)1000000000ULL)
/**
* @macro
* :macro:`NGTCP2_MILLISECONDS` is a count of tick which corresponds
* to 1 millisecond.
*/
-#define NGTCP2_MILLISECONDS ((uint64_t)1000000ULL)
+#define NGTCP2_MILLISECONDS ((ngtcp2_duration)1000000ULL)
/**
* @macro
* :macro:`NGTCP2_MICROSECONDS` is a count of tick which corresponds
* to 1 microsecond.
*/
-#define NGTCP2_MICROSECONDS ((uint64_t)1000ULL)
+#define NGTCP2_MICROSECONDS ((ngtcp2_duration)1000ULL)
/**
* @macro
* :macro:`NGTCP2_NANOSECONDS` is a count of tick which corresponds to
* 1 nanosecond.
*/
-#define NGTCP2_NANOSECONDS ((uint64_t)1ULL)
+#define NGTCP2_NANOSECONDS ((ngtcp2_duration)1ULL)
/**
* @macrosection
/**
* @macro
*
- * :macro:`NGTCP2_PKT_FLAG_LONG_FORM` indicates the Long packet
+ * :macro:`NGTCP2_PKT_FLAG_LONG_FORM` indicates the Long header packet
* header.
*/
#define NGTCP2_PKT_FLAG_LONG_FORM 0x01u
NGTCP2_EXTERN void ngtcp2_transport_params_del(ngtcp2_transport_params *params,
const ngtcp2_mem *mem);
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_version_cid` is a convenient struct to store the
+ * result of `ngtcp2_pkt_decode_version_cid`.
+ */
+typedef struct ngtcp2_version_cid {
+ /**
+ * :member:`version` stores QUIC version.
+ */
+ uint32_t version;
+ /**
+ * :member:`dcid` points to the Destination Connection ID.
+ */
+ const uint8_t *dcid;
+ /**
+ * :member:`dcidlen` is the length of the Destination Connection ID
+ * pointed by :member:`dcid`.
+ */
+ size_t dcidlen;
+ /**
+ * :member:`scid` points to the Source Connection ID.
+ */
+ const uint8_t *scid;
+ /**
+ * :member:`scidlen` is the length of the Source Connection ID
+ * pointed by :member:`scid`.
+ */
+ size_t scidlen;
+} ngtcp2_version_cid;
+
/**
* @function
*
* Longer Connection ID is only valid if the version is unsupported
* QUIC version.
*
- * If the given packet is Long packet, this function extracts the
- * version from the packet and assigns it to |*pversion|. It also
+ * If the given packet is Long header packet, this function extracts
+ * the version from the packet and assigns it to
+ * :member:`dest->version <ngtcp2_version_cid.version>`. It also
* extracts the pointer to the Destination Connection ID and its
- * length and assigns them to |*pdcid| and |*pdcidlen| respectively.
- * Similarly, it extracts the pointer to the Source Connection ID and
- * its length and assigns them to |*pscid| and |*pscidlen|
- * respectively.
- *
- * If the given packet is Short packet, |*pversion| will be 0,
- * |*pscid| will be ``NULL``, and |*pscidlen| will be 0. Because the
- * Short packet does not have the length of Destination Connection ID,
- * the caller has to pass the length in |short_dcidlen|. This
- * function extracts the pointer to the Destination Connection ID and
- * assigns it to |*pdcid|. |short_dcidlen| is assigned to
- * |*pdcidlen|.
+ * length and assigns them to :member:`dest->dcid
+ * <ngtcp2_version_cid.dcid>` and :member:`dest->dcidlen
+ * <ngtcp2_version_cid.dcidlen>` respectively. Similarly, it extracts
+ * the pointer to the Source Connection ID and its length and assigns
+ * them to :member:`dest->scid <ngtcp2_version_cid.scid>` and
+ * :member:`dest->scidlen <ngtcp2_version_cid.scidlen>` respectively.
+ *
+ * If the given packet is Short header packet, :member:`dest->version
+ * <ngtcp2_version_cid.version>` will be 0, :member:`dest->scid
+ * <ngtcp2_version_cid.scid>` will be ``NULL``, and
+ * :member:`dest->scidlen <ngtcp2_version_cid.scidlen>` will be 0.
+ * Because the Short header packet does not have the length of
+ * Destination Connection ID, the caller has to pass the length in
+ * |short_dcidlen|. This function extracts the pointer to the
+ * Destination Connection ID and assigns it to :member:`dest->dcid
+ * <ngtcp2_version_cid.dcid>`. |short_dcidlen| is assigned to
+ * :member:`dest->dcidlen <ngtcp2_version_cid.dcidlen>`.
*
* If Version Negotiation is required, this function returns
* :macro:`NGTCP2_ERR_VERSION_NEGOTIATION`. Unlike the other error
- * cases, all output parameters are assigned as described above.
+ * cases, all fields of |dest| are assigned as described above.
*
* This function returns 0 if it succeeds. Otherwise, one of the
* following negative error code:
* :macro:`NGTCP2_ERR_VERSION_NEGOTIATION`
* Version Negotiation packet should be sent.
*/
-NGTCP2_EXTERN int
-ngtcp2_pkt_decode_version_cid(uint32_t *pversion, const uint8_t **pdcid,
- size_t *pdcidlen, const uint8_t **pscid,
- size_t *pscidlen, const uint8_t *data,
- size_t datalen, size_t short_dcidlen);
+NGTCP2_EXTERN int ngtcp2_pkt_decode_version_cid(ngtcp2_version_cid *dest,
+ const uint8_t *data,
+ size_t datalen,
+ size_t short_dcidlen);
/**
* @function
/**
* @function
*
- * `ngtcp2_pkt_decode_hd_short` decodes QUIC short packet header in
- * |pkt| of length |pktlen|. |dcidlen| is the length of DCID in
- * packet header. Short packet does not encode the length of
- * connection ID, thus we need the input from the outside. This
- * function only parses the input just before packet number field.
- * This function can handle Connection ID up to
+ * `ngtcp2_pkt_decode_hd_short` decodes QUIC short header packet
+ * header in |pkt| of length |pktlen|. |dcidlen| is the length of
+ * DCID in packet header. Short header packet does not encode the
+ * length of connection ID, thus we need the input from the outside.
+ * This function only parses the input just before packet number
+ * field. This function can handle Connection ID up to
* :macro:`NGTCP2_MAX_CIDLEN`. Consider to use
* `ngtcp2_pkt_decode_version_cid` to get longer Connection ID. It
* stores the result in the object pointed by |dest|, and returns the
* This callback function must return 0 if it succeeds, or
* :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library call
* return immediately.
- *
- * TODO: Define error code for TLS stack failure. Suggestion:
- * NGTCP2_ERR_CRYPTO.
*/
typedef int (*ngtcp2_client_initial)(ngtcp2_conn *conn, void *user_data);
* The callback function must return 0 if it succeeds. If an error
* occurs, return :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the
* library call return immediately.
- *
- * TODO: Define error code for TLS stack failure. Suggestion:
- * NGTCP2_ERR_CRYPTO.
*/
typedef int (*ngtcp2_recv_client_initial)(ngtcp2_conn *conn,
const ngtcp2_cid *dcid,
* @functypedef
*
* :type:`ngtcp2_recv_retry` is invoked when Retry packet is received.
- * This callback is client only.
+ * This callback is client use only.
*
* Application must regenerate packet protection key, IV, and header
* protection key for Initial packets using the destination connection
- * ID obtained by `ngtcp2_conn_get_dcid()` and install them by calling
- * `ngtcp2_conn_install_initial_key()`.
+ * ID obtained by :member:`hd->scid <ngtcp2_pkt_hd.scid>` and install
+ * them by calling `ngtcp2_conn_install_initial_key()`.
*
- * 0-RTT data accepted by the ngtcp2 library will be retransmitted by
- * the library automatically.
+ * 0-RTT data accepted by the ngtcp2 library will be automatically
+ * retransmitted as 0-RTT data by the library.
*
* The callback function must return 0 if it succeeds. Returning
* :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return
* The implementation of this callback must encrypt |plaintext| using
* the negotiated cipher suite and write the ciphertext into the
* buffer pointed by |dest|. |dest| has enough capacity to store the
- * ciphertext.
+ * ciphertext and any additional AEAD tag data.
*
* |dest| and |plaintext| may point to the same buffer.
*
* @functypedef
*
* :type:`ngtcp2_hp_mask` is invoked when the ngtcp2 library asks the
- * application to produce mask to encrypt or decrypt packet header.
+ * application to produce a mask to encrypt or decrypt packet header.
* The encryption cipher is |hp|. |hp_ctx| is the cipher context
* object which is initialized with header protection key. The sample
* is passed as |sample| which is :macro:`NGTCP2_HP_SAMPLELEN` bytes
* The implementation of this callback must produce a mask using the
* header protection cipher suite specified by QUIC specification and
* write the result into the buffer pointed by |dest|. The length of
- * mask must be at least :macro:`NGTCP2_HP_MASKLEN`. The library only
- * uses the first :macro:`NGTCP2_HP_MASKLEN` bytes of the produced
- * mask. The buffer pointed by |dest| is guaranteed to have at least
- * :macro:`NGTCP2_HP_SAMPLELEN` bytes available for convenience.
+ * the mask must be at least :macro:`NGTCP2_HP_MASKLEN`. The library
+ * only uses the first :macro:`NGTCP2_HP_MASKLEN` bytes of the
+ * produced mask. The buffer pointed by |dest| is guaranteed to have
+ * at least :macro:`NGTCP2_HP_SAMPLELEN` bytes available for
+ * convenience.
*
* The callback function must return 0 if it succeeds, or
* :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library call
* :macro:`NGTCP2_STREAM_DATA_FLAG_FIN` is nonzero, this portion of
* the data is the last data in this stream. |offset| is the offset
* where this data begins. The library ensures that data is passed to
- * the application in the non-decreasing order of |offset|. The data
- * is passed as |data| of length |datalen|. |datalen| may be 0 if and
- * only if |fin| is nonzero.
+ * the application in the non-decreasing order of |offset| without any
+ * overlap. The data is passed as |data| of length |datalen|.
+ * |datalen| may be 0 if and only if |fin| is nonzero.
*
* If :macro:`NGTCP2_STREAM_DATA_FLAG_EARLY` is set in |flags|, it
* indicates that a part of or whole data was received in 0RTT packet
* which is called when stream data is acked, and application can free
* the data. The acked range of data is [offset, offset + datalen).
* For a given stream_id, this callback is called sequentially in
- * increasing order of |offset|. |datalen| is normally strictly
- * greater than 0. One exception is that when a packet which includes
- * STREAM frame which has fin flag set, and 0 length data, this
- * callback is invoked with 0 passed as |datalen|.
+ * increasing order of |offset| without any overlap. |datalen| is
+ * normally strictly greater than 0. One exception is that when a
+ * packet which includes STREAM frame which has fin flag set, and 0
+ * length data, this callback is invoked with 0 passed as |datalen|.
*
* If a stream is closed prematurely and stream data is still
* in-flight, this callback function is not called for those data.
* :type:`ngtcp2_lost_datagram` is invoked when a packet which
* contains DATAGRAM frame which is identified by |dgram_id| is
* declared lost. |dgram_id| is the valued passed to
- * `ngtcp2_conn_writev_datagram`.
+ * `ngtcp2_conn_writev_datagram`. Note that the loss might be
+ * spurious, and DATAGRAM frame might be acknowledged later.
*
* The callback function must return 0 if it succeeds, or
* :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library return
* Buffer is too small.
* :macro:`NGTCP2_ERR_CALLBACK_FAILURE`
* Callback function failed.
+ * :macro:`NGTCP2_ERR_INVALID_ARGUMENT`
+ * :member:`odcid->datalen <ngtcp2_cid.datalen>` is less than
+ * :macro:`NGTCP2_MIN_INITIAL_DCIDLEN`.
*/
NGTCP2_EXTERN ngtcp2_ssize ngtcp2_pkt_write_retry(
uint8_t *dest, size_t destlen, uint32_t version, const ngtcp2_cid *dcid,
* Retry packet should be sent.
* :macro:`NGTCP2_ERR_INVALID_ARGUMENT`
* The packet is not acceptable for the very first packet to a new
- * connection; or it failed to parse the packet header.
+ * connection; or the function failed to parse the packet header.
*/
NGTCP2_EXTERN int ngtcp2_accept(ngtcp2_pkt_hd *dest, const uint8_t *pkt,
size_t pktlen);
* `ngtcp2_conn_client_new` creates new :type:`ngtcp2_conn`, and
* initializes it as client. |dcid| is randomized destination
* connection ID. |scid| is source connection ID.
- * |client_chosen_version| is a QUIC version that a cilent chooses.
+ * |client_chosen_version| is a QUIC version that a client chooses.
* |path| is the network path where this QUIC connection is being
* established and must not be ``NULL``. |callbacks|, |settings|, and
* |params| must not be ``NULL``, and the function make a copy of each
* functions.
*
* This function returns 0 if it succeeds, or negative error codes.
- * In general, if the error code which satisfies
- * `ngtcp2_err_is_fatal(err) <ngtcp2_err_is_fatal>` != 0 is returned,
- * the application should just close the connection by calling
- * `ngtcp2_conn_write_connection_close` or just delete the QUIC
- * connection using `ngtcp2_conn_del`. It is undefined to call the
- * other library functions. If :macro:`NGTCP2_ERR_RETRY` is returned,
- * application must be a server and it must perform address validation
- * by sending Retry packet and close the connection. If
+ * If :macro:`NGTCP2_ERR_RETRY` is returned, application must be a
+ * server and it must perform address validation by sending Retry
+ * packet and discard the connection state. If
* :macro:`NGTCP2_ERR_DROP_CONN` is returned, server application must
* drop the connection silently (without sending any CONNECTION_CLOSE
- * frame) and discard connection state.
+ * frame) and discard connection state. If
+ * :macro:`NGTCP2_ERR_DRAINING` is returned, a connection has entered
+ * the draining state, and no further packet transmission is allowed.
+ * If :macro:`NGTCP2_ERR_CRYPTO` is returned, the error happened in
+ * TLS stack and `ngtcp2_conn_get_tls_alert` returns TLS alert if set.
+ *
+ * If any other negative errors are returned, call
+ * `ngtcp2_conn_write_connection_close` to get terminal packet, and
+ * sending it makes QUIC connection enter the closing state.
*/
NGTCP2_EXTERN int
ngtcp2_conn_read_pkt_versioned(ngtcp2_conn *conn, const ngtcp2_path *path,
* @function
*
* `ngtcp2_conn_install_rx_key` installs packet protection keying
- * materials for decrypting Short packets. |secret| of length
+ * materials for decrypting Short header packets. |secret| of length
* |secretlen| is the decryption secret which is used to derive keying
* materials passed to this function. |aead_ctx| is AEAD cipher
* context object which must be initialized with a decryption key.
* @function
*
* `ngtcp2_conn_install_tx_key` installs packet protection keying
- * materials for encrypting Short packets. |secret| of length
+ * materials for encrypting Short header packets. |secret| of length
* |secretlen| is the encryption secret which is used to derive keying
* materials passed to this function. |aead_ctx| is AEAD cipher
* context object which must be initialized with an encryption key.
*
* :macro:`NGTCP2_ERR_NOMEM`
* Out of memory
- * :macro:`NGTCP2_ERR_STREAM_NOT_FOUND`
- * Stream does not exist
*/
NGTCP2_EXTERN int ngtcp2_conn_shutdown_stream(ngtcp2_conn *conn,
int64_t stream_id,
*
* :macro:`NGTCP2_ERR_NOMEM`
* Out of memory
- * :macro:`NGTCP2_ERR_STREAM_NOT_FOUND`
- * Stream does not exist
*/
NGTCP2_EXTERN int ngtcp2_conn_shutdown_stream_write(ngtcp2_conn *conn,
int64_t stream_id,
*
* :macro:`NGTCP2_ERR_NOMEM`
* Out of memory
- * :macro:`NGTCP2_ERR_STREAM_NOT_FOUND`
- * Stream does not exist
*/
NGTCP2_EXTERN int ngtcp2_conn_shutdown_stream_read(ngtcp2_conn *conn,
int64_t stream_id,
* @function
*
* `ngtcp2_conn_is_in_closing_period` returns nonzero if |conn| is in
- * closing period.
+ * the closing period.
*/
NGTCP2_EXTERN int ngtcp2_conn_is_in_closing_period(ngtcp2_conn *conn);
* @function
*
* `ngtcp2_conn_is_in_draining_period` returns nonzero if |conn| is in
- * draining period.
+ * the draining period.
*/
NGTCP2_EXTERN int ngtcp2_conn_is_in_draining_period(ngtcp2_conn *conn);
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
- * :macro:`NGTCP2_ERR_STREAM_NOT_FOUND`
- * Stream was not found
+ * :macro:`NGTCP2_ERR_NOMEM`
+ * Out of memory.
*/
NGTCP2_EXTERN int ngtcp2_conn_extend_max_stream_offset(ngtcp2_conn *conn,
int64_t stream_id,
*/
NGTCP2_EXTERN size_t ngtcp2_conn_get_send_quantum(ngtcp2_conn *conn);
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_stream_loss_count` returns the number of packets
+ * that contain STREAM frame for a stream identified by |stream_id|
+ * and are declared to be lost. The number may include the spurious
+ * losses. If no stream identified by |stream_id| is found, this
+ * function returns 0.
+ */
+NGTCP2_EXTERN size_t ngtcp2_conn_get_stream_loss_count(ngtcp2_conn *conn,
+ int64_t stream_id);
+
/**
* @function
*
* `ngtcp2_crypto_ctx_tls` initializes |ctx| by extracting negotiated
* ciphers and message digests from native TLS session
* |tls_native_handle|. This is used for encrypting/decrypting
- * Handshake and Short packets.
+ * Handshake and Short header packets.
*
* If libngtcp2_crypto_openssl is linked, |tls_native_handle| must be
* a pointer to SSL object.
*
* Version number of the ngtcp2 library release.
*/
-#define NGTCP2_VERSION "0.7.0"
+#define NGTCP2_VERSION "0.8.0"
/**
* @macro
* number, 8 bits for minor and 8 bits for patch. Version 1.2.3
* becomes 0x010203.
*/
-#define NGTCP2_VERSION_NUM 0x000700
+#define NGTCP2_VERSION_NUM 0x000800
#endif /* VERSION_H */