Skip to content

Commit 0bace31

Browse files
committed
Implement DTLSv1_get_timeout and DTLSv1_handle_timeout
wolfSSL_DTLSv1_get_timeout always returned 0 and reported the full timeout instead of the time left, and wolfSSL_DTLSv1_handle_timeout was a stub that did nothing. OpenSSL style event loops that wait for get_timeout and then call handle_timeout never retransmitted a lost flight. Track two timers on the object. The retransmission timer restarts whenever a handshake message is stored for retransmission (each DTLS 1.2 pool save and DTLS 1.3 retransmit record), whenever the flight is resent, either on a timeout or because the peer retransmitted or acked part of it, including a resend that hit WANT_WRITE, on every timeout backoff, and when a valid handshake message, or in DTLS 1.3 an ACK that leaves nothing to retransmit, resets a backed off timeout to its initial value. Restarting on every stored message rather than only on an empty list keeps a renegotiation or post-handshake flight from inheriting the start time of an older flight that is still kept. A DTLS 1.2 flight counts as sent only in the states DtlsMsgPoolSend accepts for a retransmission, so no timer runs while a flight is still being written or waits for an asynchronous operation. A DTLS 1.3 flight counts as sent while records are kept for it. get_timeout and handle_timeout report no retransmission timer while the handshake is suspended on asynchronous crypto, non-blocking OCSP or a chain verify callback, so a slow signature cannot trigger a retransmission of a partly sent flight. The DTLS 1.3 ack timer runs for a quarter of the timeout while received records wait for an ack that can be sent. It starts when the first of those records is seen and whenever the quick timeout flag is set again. DtlsTimersRunning() reports which timer runs and replaces the inline check in EmbedReceiveFrom, which keeps its behaviour. The timers use TimeNowInMilliseconds, which is now also built for DTLS, so DTLS 1.2 builds without session tickets, PSK or TLS 1.3 no longer count whole seconds. Whole seconds stay the fallback where TLS 1.3 tickets, PSK and DTLS 1.3 do not require a millisecond clock. With USER_TICKS, or on a platform that would land in the gettimeofday() default without HAVE_GETTIMEOFDAY, the library defines no TimeNowInMilliseconds and the timers count LowResTimer seconds instead. Ports with only a seconds clock, such as Xilinx or Nucleus, keep building, and a USER_TICKS application that supplies its own TimeNowInMilliseconds still links. The DEOS clock now reads the tick counter through a const volatile pointer like the DEOS LowResTimer, since DTLS 1.2 builds now compile it. The Zephyr clock skips k_cpu_idle() on native_posix and native_sim like the Zephyr LowResTimer, since idling there advances simulated time and the DTLS timers now read this clock. CMake now exports HAVE_GETTIMEOFDAY to config.h as autotools does. The ladder marks its whole second branches and DtlsTimerLowRes reports that to the timers, so internal.h needs no second copy of the ladder. With a whole second clock the timers round elapsed time down by one tick so a timer never expires early. The ack timer is not rounded down, since an early ACK only makes the peer resend the records it is missing. A timeout of 0 means no timeout, so no timer runs. get_timeout reports at most DTLS_TIMEOUT_MAX seconds, which also keeps the millisecond count from overflowing. DtlsTimerNowMs and DtlsTimerLowRes are visible to the tests and get WOLFSSL_API_PREFIX_MAP aliases. Fix the DTLS 1.3 quick timeout. Dtls13RtxTimeout cleared the flag when it sent the ACKs and then checked it, so a timeout taken after the quarter wait also retransmitted the flight and doubled the timeout. Dtls13RtxTimeout now takes the timer that expired. handle_timeout passes the timer it found expired. The receive path and wolfSSL_dtls_got_timeout do not know how long the caller waited, so with the quick timeout flag set they send only the ACKs while the buffered flight has waited less than half its timeout, and retransmit with backoff once it waited longer. Without a clock a quick timeout only sends the ACKs and the next full timeout retransmits. A quick wait stays under half the timeout and a full one reaches it. A whole second clock is rounded down as for the timers, so a full wait can read as a quick one and retransmit one timeout later. With nothing buffered to retransmit, a timeout with the flag set only sends the ACKs instead of backing off. Flushing the seen records also clears the flag, so a flag left over from the previous flight no longer turns the next full timeout into an ACK-only one. A timeout within DTLS13_MIN_RTX_INTERVAL of the last retransmission no longer doubles the timeout when Dtls13RtxSendBuffered holds the retransmission back. Dtls13RtxSendBuffered now uses Dtls13RtxWaitMs for that check, and a clock that stepped back counts as elapsed, so it no longer holds the retransmission back until the clock catches up. test_dtls_timeout clears the last retransmission time before its second timeout, which falls within that interval of the first one. test_dtls_drop_client_ack takes one timeout instead of two, since the first one no longer finds a flag left over from the handshake. get_timeout now returns 1 with the time left while a timer runs and 0 otherwise, and no longer fills in dtls_timeout when no timer runs, so callers must check the return value. The time left includes any wait before DTLS 1.3 may retransmit again. No timer runs while the object's own output is still pending, so handle_timeout cannot queue a second copy of a blocked write. handle_timeout returns 0 when no timer expired. For an expired retransmission timer it sends any pending ACKs and retransmits with backoff, and returns 1. For an expired DTLS 1.3 ack timer it only sends the ACKs, without retransmitting or backing off, and returns 1. It returns -1 on error, including when the timeout reached its maximum, the ACK hits WANT_WRITE or the timers cannot be read under WOLFSSL_RW_THREADED, which also sets the error, while get_timeout returns 0. At the maximum timeout DTLS 1.3 now sets SOCKET_ERROR_E, as DTLS 1.2 does, instead of -1. Both return 0 when NO_ASN_TIME leaves no clock. Under WOLFSSL_RW_THREADED the DTLS 1.3 ack timer state now sits behind the dtls13Rtx mutex. The read thread sets the ack timer start and the quick timeout flag with it held, the timeout handler reads and clears the flag and the pending ACK state with it held, and get_timeout reads the start in the same locked section as the flag, so it never pairs a set flag with a stale start.
1 parent ebc2d96 commit 0bace31

8 files changed

Lines changed: 1315 additions & 103 deletions

File tree

‎cmake/config.in‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
/* Define to 1 if you have the `getaddrinfo' function. */
2020
#cmakedefine HAVE_GETADDRINFO @HAVE_GETADDRINFO@
2121

22+
/* Define to 1 if you have the `gettimeofday' function. */
23+
#cmakedefine HAVE_GETTIMEOFDAY @HAVE_GETTIMEOFDAY@
24+
2225
/* Define to 1 if you have the `gmtime_r' function. */
2326
#cmakedefine HAVE_GMTIME_R @HAVE_GMTIME_R@
2427

‎src/dtls13.c‎

Lines changed: 101 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,10 @@ int Dtls13RtxAddAck(WOLFSSL* ssl, w64wrapper epoch, w64wrapper seq)
782782
return MEMORY_E;
783783
}
784784

785+
#ifndef NO_ASN_TIME
786+
if (ssl->dtls13Rtx.seenRecords == NULL)
787+
ssl->dtls_ack_timer_start = DtlsTimerNowMs();
788+
#endif
785789
*prevNext = rn;
786790
rn->next = cur;
787791
ssl->dtls13Rtx.seenRecordsCount++;
@@ -827,6 +831,7 @@ static void Dtls13RtxFlushAcks(WOLFSSL* ssl)
827831

828832
ssl->dtls13Rtx.seenRecords = NULL;
829833
ssl->dtls13Rtx.seenRecordsCount = 0;
834+
ssl->dtls13FastTimeout = 0;
830835
#ifdef WOLFSSL_RW_THREADED
831836
wc_UnLockMutex(&ssl->dtls13Rtx.mutex);
832837
#endif
@@ -1039,8 +1044,10 @@ static int Dtls13SendOneFragmentRtx(WOLFSSL* ssl,
10391044
handshakeType, hashOutput, Dtls13SendNow(ssl, handshakeType));
10401045

10411046
if (rtxRecord != NULL) {
1042-
if (ret == 0 || ret == WC_NO_ERR_TRACE(WANT_WRITE))
1047+
if (ret == 0 || ret == WC_NO_ERR_TRACE(WANT_WRITE)) {
10431048
Dtls13RtxAddRecord(&ssl->dtls13Rtx, rtxRecord);
1049+
DtlsTimerStart(ssl);
1050+
}
10441051
else
10451052
Dtls13FreeRtxBufferRecord(ssl, rtxRecord);
10461053
}
@@ -1628,8 +1635,20 @@ int Dtls13RecordRecvd(WOLFSSL* ssl)
16281635
if (ssl->curRL.type != handshake)
16291636
return 0;
16301637

1631-
if (!ssl->options.dtls13SendMoreAcks)
1638+
if (!ssl->options.dtls13SendMoreAcks) {
1639+
#ifdef WOLFSSL_RW_THREADED
1640+
if (wc_LockMutex(&ssl->dtls13Rtx.mutex) != 0)
1641+
return BAD_MUTEX_E;
1642+
#endif
1643+
#ifndef NO_ASN_TIME
1644+
if (!ssl->dtls13FastTimeout)
1645+
ssl->dtls_ack_timer_start = DtlsTimerNowMs();
1646+
#endif
16321647
ssl->dtls13FastTimeout = 1;
1648+
#ifdef WOLFSSL_RW_THREADED
1649+
wc_UnLockMutex(&ssl->dtls13Rtx.mutex);
1650+
#endif
1651+
}
16331652

16341653
/* Acking happens in Dtls13RtxAddAckForCurRecord(). */
16351654
return 0;
@@ -1655,26 +1674,18 @@ static int Dtls13RtxSendBuffered(WOLFSSL* ssl)
16551674
byte* output;
16561675
int isLast;
16571676
int sendSz;
1658-
#ifndef NO_ASN_TIME
1659-
#ifdef WOLFSSL_32BIT_MILLI_TIME
1660-
word32 now;
1661-
#else
1662-
sword64 now;
1663-
#endif
1664-
#endif
16651677
int ret;
16661678

16671679
WOLFSSL_ENTER("Dtls13RtxSendBuffered");
16681680

16691681
#ifndef NO_ASN_TIME
1670-
now = TimeNowInMilliseconds();
1671-
if (now - ssl->dtls13Rtx.lastRtx < DTLS13_MIN_RTX_INTERVAL) {
1682+
if (Dtls13RtxWaitMs(ssl) > 0) {
16721683
#ifdef WOLFSSL_DEBUG_TLS
16731684
WOLFSSL_MSG("Avoid too fast retransmission");
16741685
#endif /* WOLFSSL_DEBUG_TLS */
16751686
return 0;
16761687
}
1677-
ssl->dtls13Rtx.lastRtx = now;
1688+
ssl->dtls13Rtx.lastRtx = TimeNowInMilliseconds();
16781689
#endif
16791690

16801691
r = ssl->dtls13Rtx.rtxRecords;
@@ -1731,13 +1742,15 @@ static int Dtls13RtxSendBuffered(WOLFSSL* ssl)
17311742
/* this fragment will be sent eventually. Move it to the end of the
17321743
list so next time we start with a new one. */
17331744
Dtls13RtxMoveToEndOfList(ssl, prevNext, r);
1745+
DtlsTimerStart(ssl);
17341746
return ret;
17351747
}
17361748

17371749
prevNext = &r->next;
17381750
r = r->next;
17391751
}
17401752

1753+
DtlsTimerStart(ssl);
17411754
return 0;
17421755
}
17431756

@@ -3418,6 +3431,7 @@ int Dtls13DoScheduledWork(WOLFSSL* ssl)
34183431
*tail = ssl->dtls13Rtx.seenRecords;
34193432
ssl->dtls13Rtx.seenRecords = NULL;
34203433
ssl->dtls13Rtx.seenRecordsCount = 0;
3434+
ssl->dtls13FastTimeout = 0;
34213435
}
34223436
else {
34233437
/* Queue already at the cap: drop this batch. */
@@ -3456,39 +3470,89 @@ int Dtls13DoScheduledWork(WOLFSSL* ssl)
34563470
return 0;
34573471
}
34583472

3459-
/* Send ACKs when available after a timeout but only retransmit the last
3460-
* flight after a long timeout */
3461-
int Dtls13RtxTimeout(WOLFSSL* ssl)
3473+
/* Return 1 when seen records wait for an ACK that may be sent now. */
3474+
int Dtls13AckSendable(const WOLFSSL* ssl)
3475+
{
3476+
return ssl->dtls13Rtx.seenRecords != NULL &&
3477+
(ssl->options.serverState >= SERVER_HELLO_COMPLETE ||
3478+
ssl->options.seenUnifiedHdr);
3479+
}
3480+
3481+
/* Milliseconds until Dtls13RtxSendBuffered() retransmits again. */
3482+
word32 Dtls13RtxWaitMs(WOLFSSL* ssl)
3483+
{
3484+
#ifndef NO_ASN_TIME
3485+
#ifdef WOLFSSL_32BIT_MILLI_TIME
3486+
word32 since = TimeNowInMilliseconds() - ssl->dtls13Rtx.lastRtx;
3487+
#else
3488+
sword64 since = TimeNowInMilliseconds() - ssl->dtls13Rtx.lastRtx;
3489+
3490+
if (since < 0)
3491+
return 0;
3492+
#endif
3493+
3494+
if (since < DTLS13_MIN_RTX_INTERVAL)
3495+
return (word32)(DTLS13_MIN_RTX_INTERVAL - since);
3496+
#else
3497+
(void)ssl;
3498+
#endif
3499+
return 0;
3500+
}
3501+
3502+
/* Return 1 once the buffered flight waited half its retransmission timeout. */
3503+
static int Dtls13RtxTimerDue(WOLFSSL* ssl)
3504+
{
3505+
#ifndef NO_ASN_TIME
3506+
if (ssl->dtls13Rtx.rtxRecords == NULL || ssl->dtls_timeout <= 0)
3507+
return 0;
3508+
return DtlsTimerElapsedMs(ssl->dtls_timer_start) >=
3509+
DtlsTimerTimeoutMs(ssl) / 2;
3510+
#else
3511+
(void)ssl;
3512+
return 0;
3513+
#endif
3514+
}
3515+
3516+
/* Send ACKs after a timeout and retransmit the last flight only after a long
3517+
* timeout. expired is the DtlsTimer that expired, or 0 when not known. */
3518+
int Dtls13RtxTimeout(WOLFSSL* ssl, int expired)
34623519
{
34633520
int ret = 0;
3521+
int ackOnly = (expired == DTLS_TIMER_ACK);
3522+
int fastTimeout;
3523+
int sendAck;
34643524

3465-
/* We don't want to send acks until we have done version
3466-
* negotiation in the SH or have received a unified header in the
3467-
* DTLS record. */
3468-
if (ssl->dtls13Rtx.seenRecords != NULL &&
3469-
(ssl->options.serverState >= SERVER_HELLO_COMPLETE ||
3470-
ssl->options.seenUnifiedHdr)) {
3525+
#ifdef WOLFSSL_RW_THREADED
3526+
if (wc_LockMutex(&ssl->dtls13Rtx.mutex) != 0)
3527+
return BAD_MUTEX_E;
3528+
#endif
3529+
fastTimeout = ssl->dtls13FastTimeout;
3530+
ssl->dtls13FastTimeout = 0;
3531+
sendAck = Dtls13AckSendable(ssl);
3532+
if (sendAck)
34713533
ssl->dtls13Rtx.sendAcks = 0;
3472-
/* reset fast timeout as we are sending ACKs */
3473-
ssl->dtls13FastTimeout = 0;
3534+
#ifdef WOLFSSL_RW_THREADED
3535+
wc_UnLockMutex(&ssl->dtls13Rtx.mutex);
3536+
#endif
3537+
3538+
if (expired == 0 && fastTimeout)
3539+
ackOnly = !Dtls13RtxTimerDue(ssl);
3540+
3541+
if (sendAck) {
34743542
ret = SendDtls13Ack(ssl);
34753543
if (ret != 0)
34763544
return ret;
34773545
}
34783546

3479-
/* we have two timeouts, a shorter (dtls13FastTimeout = 1) and a longer
3480-
one. When the shorter expires we only send ACKs, as it normally means
3481-
that some messages we are waiting for don't arrive yet. But we
3482-
retransmit our buffered messages only if the longer timeout
3483-
expires. fastTimeout is 1/4 of the longer timeout */
3484-
if (ssl->dtls13FastTimeout) {
3485-
ssl->dtls13FastTimeout = 0;
3547+
if (ackOnly)
3548+
return 0;
3549+
3550+
if (Dtls13RtxWaitMs(ssl) > 0)
34863551
return 0;
3487-
}
34883552

34893553
/* Increase timeout on long timeout */
34903554
if (DtlsMsgPoolTimeout(ssl) != 0)
3491-
return WOLFSSL_FATAL_ERROR;
3555+
return SOCKET_ERROR_E;
34923556

34933557
return Dtls13RtxSendBuffered(ssl);
34943558
}
@@ -3623,6 +3687,7 @@ int DoDtls13Ack(WOLFSSL* ssl, const byte* input, word32 inputSize,
36233687
#ifndef WOLFSSL_RW_THREADED
36243688
int ret;
36253689
#endif
3690+
int hadRecords;
36263691
int i;
36273692

36283693
/* an epoch 0 ACK once encrypted only triggers retransmission */
@@ -3646,6 +3711,7 @@ int DoDtls13Ack(WOLFSSL* ssl, const byte* input, word32 inputSize,
36463711

36473712
WOLFSSL_MSG("read ack records");
36483713

3714+
hadRecords = ssl->dtls13Rtx.rtxRecords != NULL;
36493715
ackMessage = input + OPAQUE16_LEN;
36503716
for (i = 0; i < length; i += DTLS13_RN_SIZE) {
36513717
ato64(ackMessage + i, &epoch);
@@ -3670,6 +3736,9 @@ int DoDtls13Ack(WOLFSSL* ssl, const byte* input, word32 inputSize,
36703736
#endif /* HAVE_WRITE_DUP */
36713737
}
36723738

3739+
if (hadRecords && ssl->dtls13Rtx.rtxRecords == NULL)
3740+
DtlsTimeoutReset(ssl);
3741+
36733742
/* last client flight was completely acknowledged by the server. Handshake
36743743
is complete. */
36753744
if (ssl->options.side == WOLFSSL_CLIENT_END &&

0 commit comments

Comments
 (0)