diff --git a/worker/src/RTC/DtlsTransport.cpp b/worker/src/RTC/DtlsTransport.cpp index 3bb8a9657c..40e4f0d2e5 100644 --- a/worker/src/RTC/DtlsTransport.cpp +++ b/worker/src/RTC/DtlsTransport.cpp @@ -832,9 +832,9 @@ namespace RTC } MS_DUMP(""); - MS_DUMP(" state : %s", state.c_str()); - MS_DUMP(" role : %s", role.c_str()); - MS_DUMP(" handshake done: : %s", this->handshakeDone ? "yes" : "no"); + MS_DUMP(" state: %s", state.c_str()); + MS_DUMP(" role: %s", role.c_str()); + MS_DUMP(" handshake done: %s", this->handshakeDone ? "yes" : "no"); MS_DUMP(""); } @@ -1189,10 +1189,8 @@ namespace RTC return; } - int64_t read; char* data{ nullptr }; - - read = BIO_get_mem_data(this->sslBioToNetwork, &data); // NOLINT + const int64_t read = BIO_get_mem_data(this->sslBioToNetwork, &data); // NOLINT if (read <= 0) { @@ -1201,9 +1199,95 @@ namespace RTC MS_DEBUG_DEV("%" PRIu64 " bytes of DTLS data ready to sent to the peer", read); - // Notify the listener. - this->listener->OnDtlsTransportSendData( - this, reinterpret_cast(data), static_cast(read)); + if (read <= DtlsMtu) + { + // Notify the listener. + this->listener->OnDtlsTransportSendData( + this, reinterpret_cast(data), static_cast(read)); + } + else + { + // Here we have data containing one or more DTLS messages with total size + // higher than our DtlsMtu value. These DTLS messages are, in fact, DTLS + // message fragments (various fragments conform a DTLS message). Each DTLS + // message fragment must be sent in a single UDP datagram or TCP framed + // message (although various DTLS message fragments can be sent together + // because they are framed). So the question is: How to split this big + // data we have here into valid DTLS message fragments? + // + // Here the trick: + // - We called SSL_CTX_set_options() with SSL_OP_NO_QUERY_MTU (among other + // flags). + // - We called SSL_set_mtu(this->ssl, DtlsMtu). + // - We called DTLS_set_link_mtu(this->ssl, DtlsMtu). + // + // So we know that OpenSSL will split DTLS messages bigger than DtlsMtu + // into DtlsMtu bytes long chunks (of course the last chunk maybe smaller). + // So assuming that (and it behaves that way), we can follow the reverse + // logic here and split this big data into chunks of DtlsMtu (except the + // last one, of couse), so it's guaranteed that each chunk will contain a + // valid DTLS message fragment. So we notify the parent by passing each + // chunk to it, so it will encapsulate it into a single UDP datagram or + // framed TCP message. + // + // There is an scenario in which this logic would fail: + // + // - DtlsMtu is 1350 bytes. + // - OpenSSL generates 2 sequential DTLS messages to be sent to client. + // - First message is 2500 bytes long. So OpenSSL splits it into 2 DTLS + // message fragments: + // 1. First DTLS message fragment is 1350 (DtlsMtu) bytes long. + // 2. Second DTLS message fragment is 1150. + // - Second message is 500 bytes long (so no fragments needed). + // - So in SendPendingOutgoingDtlsData() we have 3000 bytes to send in + // total and there are 3 DTLS messages: + // 1. The first fragment of the first message: 1350 bytes. + // 2. The second fragment of the first message: 1150 bytes. + // 3. The second DTLS message: 500 bytes. + // - Following the above logic, SendPendingOutgoingDtlsData() will split + // those 3000 bytes as follows: + // 1. First 1350 bytes: Here we are exactly taking the first fragment of + // the first message, so all good. + // 2. Next 1350 bytes: Here we are reading the 1150 bytes of the second + // fragment of the first message plus the first 200 bytes of the + // second message. This is NOT good. + // 3. Next 300 bytes: The remaining 300 bytes of the second message. + // This is not good. + // - Client will reject the second and third UDP packets since they do not + // contain one or more valid DTLS full messages or DTLS message + // fragments. + // + // However, by experimenting I see that OpenSSL doesn't generate messages + // like those and, anyway, we may only need to send DTLS messages bigger + // than DtlsMtu during the handshake and only if our certificate is big, + // and in this scenario the problematic above sequence doesn't happen. + // + // PR with more information about this: + // https://github.com/versatica/mediasoup/pull/1343. + + MS_DEBUG_TAG( + dtls, + "data to be sent is longer than DTLS MTU, fragmenting it [len:%" PRIi64 ", DtlsMtu:%i]", + read, + DtlsMtu); + + size_t offset{ 0u }; + + while (offset < read) + { + auto* fragmentData = reinterpret_cast(data + offset); + auto fragmentLen = static_cast(read) - offset >= DtlsMtu + ? DtlsMtu + : static_cast(read) - offset; + + MS_DEBUG_DEV("sending fragment [offset:%zu, len:%zu]", offset, fragmentLen); + + // Notify the listener. + this->listener->OnDtlsTransportSendData(this, fragmentData, fragmentLen); + + offset += fragmentLen; + } + } // Clear the BIO buffer. // NOTE: the (void) avoids the -Wunused-value warning.