From 2c96b320fa2fd864db2eee662347ea3723a58acf Mon Sep 17 00:00:00 2001 From: winlin Date: Fri, 2 Jun 2023 19:00:36 +0800 Subject: [PATCH 1/4] DTLS: Use bio callback to get fragment packet. --- trunk/src/app/srs_app_rtc_dtls.cpp | 338 +++++++++-------------------- trunk/src/app/srs_app_rtc_dtls.hpp | 31 +-- 2 files changed, 116 insertions(+), 253 deletions(-) diff --git a/trunk/src/app/srs_app_rtc_dtls.cpp b/trunk/src/app/srs_app_rtc_dtls.cpp index 38f862d899..977426b336 100644 --- a/trunk/src/app/srs_app_rtc_dtls.cpp +++ b/trunk/src/app/srs_app_rtc_dtls.cpp @@ -44,17 +44,12 @@ unsigned int dtls_timer_cb(SSL* dtls, unsigned int previous_us) SrsDtlsImpl* dtls_impl = (SrsDtlsImpl*)SSL_get_ex_data(dtls, 0); srs_assert(dtls_impl); - // Double the timeout. Note that it can be 0. + // Double the timeout in us. Note that it can be 0. unsigned int timeout_us = previous_us * 2; - // If previous_us is 0, for example, the HelloVerifyRequest, we should response it ASAP. - // When got ServerHello, we should reset the timer. - if (previous_us == 0 || dtls_impl->should_reset_timer()) { - timeout_us = 50 * 1000; // in us - } - - // Never exceed the max timeout. - timeout_us = srs_min(timeout_us, 30 * 1000 * 1000); // in us + // limit the timeout in [50ms, 30s]. + timeout_us = srs_max(timeout_us, 50 * 1000); + timeout_us = srs_min(timeout_us, 30 * 1000 * 1000); srs_info("DTLS: ARQ timer cb timeout=%ums, previous=%ums", timeout_us/1000, previous_us/1000); @@ -425,6 +420,35 @@ SrsDtlsImpl::~SrsDtlsImpl() } } +long srs_dtls_bio_out_callback(BIO* bio, int cmd, const char* argp, int argi, long argl, long ret) +{ + long r0 = (BIO_CB_RETURN & cmd) ? ret : 1; + if (cmd == BIO_CB_WRITE && argp && argi > 0) { + SrsDtlsImpl* dtls = (SrsDtlsImpl*)BIO_get_callback_arg(bio); + srs_error_t err = dtls->write_dtls_data((void*)argp, argi); + if (err != srs_success) { + srs_warn("ignore err %s", srs_error_desc(err).c_str()); + } + srs_freep(err); + } + return r0; +} + +srs_error_t SrsDtlsImpl::write_dtls_data(void* data, int size) +{ + srs_error_t err = srs_success; + + if (size > 0 && (err = callback_->write_dtls_data(data, size)) != srs_success) { + return srs_error_wrap(err, "dtls send size=%u, data=[%s]", size, + srs_string_dumps_hex((char*)data, size, 32).c_str()); + } + + // Logging when got SSL original data. + state_trace((uint8_t*)data, size, false, 0, 0, false); + + return err; +} + srs_error_t SrsDtlsImpl::initialize(std::string version, std::string role) { srs_error_t err = srs_success; @@ -446,10 +470,13 @@ srs_error_t SrsDtlsImpl::initialize(std::string version, std::string role) SSL_set_ex_data(dtls, 0, this); SSL_set_info_callback(dtls, ssl_on_info); - // set dtls fragment + // We have set the MTU to fragment the DTLS packet. It is important to note that the packet is split + // to ensure that each handshake packet is smaller than the MTU. // @see https://stackoverflow.com/questions/62413602/openssl-server-packets-get-fragmented-into-270-bytes-per-packet SSL_set_options(dtls, SSL_OP_NO_QUERY_MTU); SSL_set_mtu(dtls, DTLS_FRAGMENT_MAX_SIZE); + // See https://github.com/versatica/mediasoup/pull/217 + DTLS_set_link_mtu(dtls, DTLS_FRAGMENT_MAX_SIZE); // @see https://linux.die.net/man/3/openssl_version_number // MM NN FF PP S @@ -464,6 +491,7 @@ srs_error_t SrsDtlsImpl::initialize(std::string version, std::string role) DTLS_set_timer_cb(dtls, dtls_timer_cb); #endif + // Setup memory BIO. if ((bio_in = BIO_new(BIO_s_mem())) == NULL) { return srs_error_new(ERROR_OpenSslBIONew, "BIO_new in"); } @@ -473,11 +501,51 @@ srs_error_t SrsDtlsImpl::initialize(std::string version, std::string role) return srs_error_new(ERROR_OpenSslBIONew, "BIO_new out"); } + // Please be aware that it is necessary to use a callback to obtain the packet to be written out. It is + // imperative that BIO_get_mem_data is not used to retrieve the packet, as it returns all the bytes that + // need to be sent out. + // For example, if MTU is set to 1200, and we got two DTLS packets to sendout: + // ServerHello, 95bytes. + // Certificate, 1105+143=1248bytes. + // If use BIO_get_mem_data, it will return 95+1248=1343bytes, which is larger than MTU 1200. + // If use callback, it will return two UDP packets: + // ServerHello+Certificate(Frament) = 95+1105=1200bytes. + // Certificate(Fragment) = 143bytes. + // Note that there should be more packets in real world, like ServerKeyExchange, CertificateRequest, + // and ServerHelloDone. Here we just use two packets for example. + BIO_set_callback(bio_out, srs_dtls_bio_out_callback); + BIO_set_callback_arg(bio_out, (char*)this); + SSL_set_bio(dtls, bio_in, bio_out); return err; } +srs_error_t SrsDtlsImpl::start_active_handshake() +{ + srs_error_t err = srs_success; + + // During initialization, we only need to call SSL_do_handshake once because SSL_read consumes + // the handshake message if the handshake is incomplete. + // To simplify maintenance, we initiate the handshake for both the DTLS server and client after + // sending out the ICE response in the start_active_handshake function. It's worth noting that + // although the DTLS server may receive the ClientHello immediately after sending out the ICE + // response, this shouldn't be an issue as the handshake function is called before any DTLS + // packets are received. + int r0 = SSL_do_handshake(dtls); + int r1 = SSL_get_error(dtls, r0); ERR_clear_error(); + // Fatal SSL error, for example, no available suite when peer is DTLS 1.0 while we are DTLS 1.2. + if (r0 < 0 && (r1 != SSL_ERROR_NONE && r1 != SSL_ERROR_WANT_READ && r1 != SSL_ERROR_WANT_WRITE)) { + return srs_error_new(ERROR_RTC_DTLS, "handshake r0=%d, r1=%d", r0, r1); + } + + if ((err = start_arq()) != srs_success) { + return srs_error_wrap(err, "start arq"); + } + + return err; +} + srs_error_t SrsDtlsImpl::on_dtls(char* data, int nb_data) { srs_error_t err = srs_success; @@ -497,65 +565,22 @@ srs_error_t SrsDtlsImpl::do_on_dtls(char* data, int nb_data) // When already done, only for us, we still got message from client, // it might be our response is lost, or application data. if (handshake_done_for_us) { - srs_info("DTLS: After done, got %d bytes", nb_data); + srs_trace("DTLS: After done, got %d bytes", nb_data); } + // Feed the received DTLS packets to BIO; we will consume them later. int r0 = 0; - // TODO: FIXME: Why reset it before writing? - if ((r0 = BIO_reset(bio_in)) != 1) { - return srs_error_new(ERROR_OpenSslBIOReset, "BIO_reset r0=%d", r0); - } - if ((r0 = BIO_reset(bio_out)) != 1) { - return srs_error_new(ERROR_OpenSslBIOReset, "BIO_reset r0=%d", r0); - } - - // Trace the detail of DTLS packet. - state_trace((uint8_t*)data, nb_data, true, r0, SSL_ERROR_NONE, false); - if ((r0 = BIO_write(bio_in, data, nb_data)) <= 0) { // TODO: 0 or -1 maybe block, use BIO_should_retry to check. return srs_error_new(ERROR_OpenSslBIOWrite, "BIO_write r0=%d", r0); } + state_trace((uint8_t*)data, nb_data, true, r0, SSL_ERROR_NONE, false); - // Always do handshake, even the handshake is done, because the last DTLS packet maybe dropped, - // so we thought the DTLS is done, but client need us to retransmit the last packet. - if ((err = do_handshake()) != srs_success) { - return srs_error_wrap(err, "do handshake"); - } - - // If there is data in bio_in, read it to let SSL consume it. - // @remark Limit the max loop, to avoid the dead loop. - for (int i = 0; i < 1024 && BIO_ctrl_pending(bio_in) > 0; i++) { - char buf[8092]; - int r0 = SSL_read(dtls, buf, sizeof(buf)); - int r1 = SSL_get_error(dtls, r0); ERR_clear_error(); - - if (r0 <= 0) { - // SSL_ERROR_ZERO_RETURN - // - // The TLS/SSL connection has been closed. If the protocol version is SSL 3.0 or higher, - // this result code is returned only if a closure alert has occurred in the protocol, - // i.e. if the connection has been closed cleanly. - // @see https://www.openssl.org/docs/man1.1.0/man3/SSL_get_error.html - // @remark Already close, never read again, because padding always exsists. - if (r1 != SSL_ERROR_WANT_READ && r1 != SSL_ERROR_WANT_WRITE) { - break; - } - - // We got data in memory, which can not read by SSL_read, generally, it's handshake data. - uint8_t* data = NULL; - int size = BIO_get_mem_data(bio_out, (char**)&data); - - // Logging when got SSL original data. - state_trace((uint8_t*)data, size, false, r0, r1, false); - - if (size > 0 && (err = callback_->write_dtls_data(data, size)) != srs_success) { - return srs_error_wrap(err, "dtls send size=%u, data=[%s]", size, - srs_string_dumps_hex((char*)data, size, 32).c_str()); - } - continue; - } - + // If there is data available in bio_in, use SSL_read to allow SSL to process it. + char buf[kRtpPacketSize]; + r0 = SSL_read(dtls, buf, sizeof(buf)); + int r1 = SSL_get_error(dtls, r0); ERR_clear_error(); + if (r0 > 0) { srs_trace("DTLS: read r0=%d, r1=%d, padding=%d, done=%d, data=[%s]", r0, r1, BIO_ctrl_pending(bio_in), handshake_done_for_us, srs_string_dumps_hex(buf, r0, 32).c_str()); @@ -565,51 +590,9 @@ srs_error_t SrsDtlsImpl::do_on_dtls(char* data, int nb_data) } } - return err; -} - -srs_error_t SrsDtlsImpl::do_handshake() -{ - srs_error_t err = srs_success; - - // Done for use, ignore handshake packets. If need to ARQ the handshake packets, - // we should use SSL_read to handle it. - if (handshake_done_for_us) { - return err; - } - - // Do handshake and get the result. - int r0 = SSL_do_handshake(dtls); - int r1 = SSL_get_error(dtls, r0); ERR_clear_error(); - - // Fatal SSL error, for example, no available suite when peer is DTLS 1.0 while we are DTLS 1.2. - if (r0 < 0 && (r1 != SSL_ERROR_NONE && r1 != SSL_ERROR_WANT_READ && r1 != SSL_ERROR_WANT_WRITE)) { - return srs_error_new(ERROR_RTC_DTLS, "handshake r0=%d, r1=%d", r0, r1); - } - - // OK, Handshake is done, note that it maybe done many times. - if (r1 == SSL_ERROR_NONE) { + // Check whether the DTLS is completed. + if (!handshake_done_for_us && SSL_is_init_finished(dtls) == 1) { handshake_done_for_us = true; - } - - // The data to send out to peer. - uint8_t* data = NULL; - int size = BIO_get_mem_data(bio_out, (char**)&data); - - // Logging when got SSL original data. - state_trace((uint8_t*)data, size, false, r0, r1, false); - - // Callback for the final output data, before send-out. - if ((err = on_final_out_data(data, size)) != srs_success) { - return srs_error_wrap(err, "handle"); - } - - if (size > 0 && (err = callback_->write_dtls_data(data, size)) != srs_success) { - return srs_error_wrap(err, "dtls send size=%u, data=[%s]", size, - srs_string_dumps_hex((char*)data, size, 32).c_str()); - } - - if (handshake_done_for_us) { if (((err = on_handshake_done()) != srs_success)) { return srs_error_wrap(err, "done"); } @@ -691,7 +674,6 @@ SrsDtlsClientImpl::SrsDtlsClientImpl(ISrsDtlsCallback* callback) : SrsDtlsImpl(c // the max dtls retry num is 12 in openssl. arq_max_retry = 12 * 2; // Max ARQ limit shared for ClientHello and Certificate. - reset_timer_ = true; } SrsDtlsClientImpl::~SrsDtlsClientImpl() @@ -709,54 +691,6 @@ srs_error_t SrsDtlsClientImpl::initialize(std::string version, std::string role) // Dtls setup active, as client role. SSL_set_connect_state(dtls); - SSL_set_max_send_fragment(dtls, DTLS_FRAGMENT_MAX_SIZE); - - return err; -} - -srs_error_t SrsDtlsClientImpl::start_active_handshake() -{ - srs_error_t err = srs_success; - - if ((err = do_handshake()) != srs_success) { - return srs_error_wrap(err, "start handshake"); - } - - if ((err = start_arq()) != srs_success) { - return srs_error_wrap(err, "start arq"); - } - - return err; -} - -bool SrsDtlsClientImpl::should_reset_timer() -{ - bool v = reset_timer_; - reset_timer_ = false; - return v; -} - -// Note that only handshake sending packets drives the state, neither ARQ nor the -// final-packets(after handshake done) drives it. -srs_error_t SrsDtlsClientImpl::on_final_out_data(uint8_t* data, int size) -{ - srs_error_t err = srs_success; - - // If we are sending client hello, change from init to new state. - if (state_ == SrsDtlsStateInit && size > 14 && data[0] == 22 && data[13] == 1) { - state_ = SrsDtlsStateClientHello; - return err; - } - - // If we are sending certificate, change from SrsDtlsStateClientHello to new state. - if (state_ == SrsDtlsStateClientHello && size > 14 && data[0] == 22 && data[13] == 11) { - state_ = SrsDtlsStateClientCertificate; - - // When we send out the certificate, we should reset the timer. - reset_timer_ = true; - srs_info("DTLS: Reset the timer for ServerHello"); - return err; - } return err; } @@ -815,8 +749,8 @@ srs_error_t SrsDtlsClientImpl::cycle() srs_error_t err = srs_success; // Limit the max retry for ARQ, to avoid infinite loop. - // Note that we set the timeout to [50, 100, 200, 400, 800, 1600, 3200, 6400, 12800, 25600, 51200] in ms, - // but the actual timeout is limit to 1s: + // Note that we set the timeout to [50, 100, 200, 400, 800, 1600, 3200, 6400, 12800, 25600, 51200] in ms + // by dtls_timer_cb, but the actual timeout is limit to 1s: // 50ms, 100ms, 200ms, 400ms, 800ms, (1000ms,600ms), (200ms,1000ms,1000ms,1000ms), // (400ms,1000ms,1000ms,1000ms,1000ms,1000ms,1000ms), ... // So when the max ARQ limit to 12 times, the max loop is about 103. @@ -836,63 +770,27 @@ srs_error_t SrsDtlsClientImpl::cycle() return err; } - // For DTLS client ARQ, the state should be specified. - if (state_ != SrsDtlsStateClientHello && state_ != SrsDtlsStateClientCertificate) { - return err; - } - // If there is a timeout in progress, it sets *out to the time remaining // and returns one. Otherwise, it returns zero. - int r0 = 0; timeval to = {0}; - if ((r0 = DTLSv1_get_timeout(dtls, &to)) == 0) { - // No timeout, for example?, wait for a default 50ms. - srs_usleep(50 * SRS_UTIME_MILLISECONDS); - continue; - } - srs_utime_t timeout = to.tv_sec + to.tv_usec; + timeval to = {0}; + int r0 = DTLSv1_get_timeout(dtls, &to); + srs_utime_t timeout = r0 == 1 ? to.tv_sec + to.tv_usec : 0; // There is timeout to wait, so we should wait, because there is no packet in openssl. if (timeout > 0) { - // Never wait too long, because we might need to retransmit other messages. - // For example, we have transmit 2 ClientHello as [50ms, 100ms] then we sleep(200ms), - // during this we reset the openssl timer to 50ms and need to retransmit Certificate, - // we still need to wait 200ms not 50ms. - timeout = srs_min(100 * SRS_UTIME_MILLISECONDS, timeout); - timeout = srs_max(50 * SRS_UTIME_MILLISECONDS, timeout); srs_usleep(timeout); continue; } - // The timeout is 0, so there must be a ARQ packet to transmit in openssl. - r0 = BIO_reset(bio_out); int r1 = SSL_get_error(dtls, r0); ERR_clear_error(); - if (r0 != 1) { - return srs_error_new(ERROR_OpenSslBIOReset, "BIO_reset r0=%d, r1=%d", r0, r1); - } - // DTLSv1_handle_timeout is called when a DTLS handshake timeout expires. If no timeout // had expired, it returns 0. Otherwise, it retransmits the previous flight of handshake // messages and returns 1. If too many timeouts had expired without progress or an error // occurs, it returns -1. - r0 = DTLSv1_handle_timeout(dtls); r1 = SSL_get_error(dtls, r0); ERR_clear_error(); - if (r0 == 0) { - continue; // No timeout had expired. - } - if (r0 != 1) { + r0 = DTLSv1_handle_timeout(dtls); + if (r0 != 0 && r0 != 1) { + int r1 = SSL_get_error(dtls, r0); ERR_clear_error(); return srs_error_new(ERROR_RTC_DTLS, "ARQ r0=%d, r1=%d", r0, r1); } - - // The data to send out to peer. - uint8_t* data = NULL; - int size = BIO_get_mem_data(bio_out, (char**)&data); - - arq_count++; - nn_arq_packets++; - state_trace((uint8_t*)data, size, false, r0, r1, true); - - if (size > 0 && (err = callback_->write_dtls_data(data, size)) != srs_success) { - return srs_error_wrap(err, "dtls send size=%u, data=[%s]", size, - srs_string_dumps_hex((char*)data, size, 32).c_str()); - } } return err; @@ -920,24 +818,6 @@ srs_error_t SrsDtlsServerImpl::initialize(std::string version, std::string role) return err; } -srs_error_t SrsDtlsServerImpl::start_active_handshake() -{ - // For DTLS server, we do nothing, because DTLS client drive it. - return srs_success; -} - -bool SrsDtlsServerImpl::should_reset_timer() -{ - // For DTLS server, we never use timer for ARQ, because DTLS client drive it. - return false; -} - -srs_error_t SrsDtlsServerImpl::on_final_out_data(uint8_t* data, int size) -{ - // No ARQ, driven by DTLS client packets. - return srs_success; -} - srs_error_t SrsDtlsServerImpl::on_handshake_done() { srs_error_t err = srs_success; @@ -955,6 +835,12 @@ bool SrsDtlsServerImpl::is_dtls_client() return false; } +srs_error_t SrsDtlsServerImpl::start_arq() +{ + // We do not initiate ARQ for the DTLS server since it is controlled by the DTLS client. + return srs_success; +} + SrsDtlsEmptyImpl::SrsDtlsEmptyImpl() : SrsDtlsImpl(NULL) { handshake_done_for_us = true; @@ -969,16 +855,6 @@ srs_error_t SrsDtlsEmptyImpl::initialize(std::string version, std::string role) return srs_success; } -srs_error_t SrsDtlsEmptyImpl::start_active_handshake() -{ - return srs_success; -} - -bool SrsDtlsEmptyImpl::should_reset_timer() -{ - return false; -} - srs_error_t SrsDtlsEmptyImpl::on_dtls(char* data, int nb_data) { return srs_success; @@ -993,11 +869,6 @@ void SrsDtlsEmptyImpl::callback_by_ssl(std::string type, std::string desc) { } -srs_error_t SrsDtlsEmptyImpl::on_final_out_data(uint8_t* data, int size) -{ - return srs_success; -} - srs_error_t SrsDtlsEmptyImpl::on_handshake_done() { return srs_success; @@ -1008,6 +879,11 @@ bool SrsDtlsEmptyImpl::is_dtls_client() return false; } +srs_error_t SrsDtlsEmptyImpl::start_arq() +{ + return srs_success; +} + SrsDtls::SrsDtls(ISrsDtlsCallback* callback) { callback_ = callback; diff --git a/trunk/src/app/srs_app_rtc_dtls.hpp b/trunk/src/app/srs_app_rtc_dtls.hpp index 6daf7f5c77..d5495c85cc 100644 --- a/trunk/src/app/srs_app_rtc_dtls.hpp +++ b/trunk/src/app/srs_app_rtc_dtls.hpp @@ -83,10 +83,6 @@ class ISrsDtlsCallback // The state for DTLS client. enum SrsDtlsState { SrsDtlsStateInit, // Start. - SrsDtlsStateClientHello, // Should start ARQ thread. - SrsDtlsStateServerHello, // We are in the first ARQ state. - SrsDtlsStateClientCertificate, // Should start ARQ thread again. - SrsDtlsStateServerDone, // We are in the second ARQ state. SrsDtlsStateClientDone, // Done. }; @@ -109,22 +105,23 @@ class SrsDtlsImpl public: SrsDtlsImpl(ISrsDtlsCallback* callback); virtual ~SrsDtlsImpl(); +public: + // Internal API for sending DTLS packets. + srs_error_t write_dtls_data(void* data, int size); public: virtual srs_error_t initialize(std::string version, std::string role); - virtual srs_error_t start_active_handshake() = 0; - virtual bool should_reset_timer() = 0; + virtual srs_error_t start_active_handshake(); virtual srs_error_t on_dtls(char* data, int nb_data); protected: srs_error_t do_on_dtls(char* data, int nb_data); - srs_error_t do_handshake(); void state_trace(uint8_t* data, int length, bool incoming, int r0, int r1, bool arq); public: srs_error_t get_srtp_key(std::string& recv_key, std::string& send_key); void callback_by_ssl(std::string type, std::string desc); protected: - virtual srs_error_t on_final_out_data(uint8_t* data, int size) = 0; virtual srs_error_t on_handshake_done() = 0; virtual bool is_dtls_client() = 0; + virtual srs_error_t start_arq() = 0; }; class SrsDtlsClientImpl : public SrsDtlsImpl, public ISrsCoroutineHandler @@ -137,21 +134,15 @@ class SrsDtlsClientImpl : public SrsDtlsImpl, public ISrsCoroutineHandler SrsDtlsState state_; // The max ARQ retry. int arq_max_retry; - // Should we reset the timer? - // It's true when init, or in state ServerHello. - bool reset_timer_; public: SrsDtlsClientImpl(ISrsDtlsCallback* callback); virtual ~SrsDtlsClientImpl(); public: virtual srs_error_t initialize(std::string version, std::string role); - virtual srs_error_t start_active_handshake(); - virtual bool should_reset_timer(); protected: - virtual srs_error_t on_final_out_data(uint8_t* data, int size); virtual srs_error_t on_handshake_done(); virtual bool is_dtls_client(); -private: +protected: srs_error_t start_arq(); void stop_arq(); public: @@ -165,12 +156,10 @@ class SrsDtlsServerImpl : public SrsDtlsImpl virtual ~SrsDtlsServerImpl(); public: virtual srs_error_t initialize(std::string version, std::string role); - virtual srs_error_t start_active_handshake(); - virtual bool should_reset_timer(); protected: - virtual srs_error_t on_final_out_data(uint8_t* data, int size); virtual srs_error_t on_handshake_done(); virtual bool is_dtls_client(); + srs_error_t start_arq(); }; class SrsDtlsEmptyImpl : public SrsDtlsImpl @@ -180,16 +169,14 @@ class SrsDtlsEmptyImpl : public SrsDtlsImpl virtual ~SrsDtlsEmptyImpl(); public: virtual srs_error_t initialize(std::string version, std::string role); - virtual srs_error_t start_active_handshake(); - virtual bool should_reset_timer(); virtual srs_error_t on_dtls(char* data, int nb_data); public: srs_error_t get_srtp_key(std::string& recv_key, std::string& send_key); void callback_by_ssl(std::string type, std::string desc); protected: - virtual srs_error_t on_final_out_data(uint8_t* data, int size); virtual srs_error_t on_handshake_done(); virtual bool is_dtls_client(); + virtual srs_error_t start_arq(); }; class SrsDtls @@ -203,7 +190,7 @@ class SrsDtls public: srs_error_t initialize(std::string role, std::string version); public: - // As DTLS client, start handshake actively, send the ClientHello packet. + // Start DTLS handshake mechanism. srs_error_t start_active_handshake(); // When got DTLS packet, may handshake packets or application data. // @remark When we are passive(DTLS server), we start handshake when got DTLS packet. From bcb62eebc9844a22b44f8020d0789a447802eb72 Mon Sep 17 00:00:00 2001 From: winlin Date: Sat, 3 Jun 2023 20:45:32 +0800 Subject: [PATCH 2/4] DTLS: Refine the packet trace and session close. --- trunk/src/app/srs_app_rtc_conn.cpp | 5 ++-- trunk/src/app/srs_app_rtc_dtls.cpp | 39 +++++++++++++++++++++++------- trunk/src/app/srs_app_rtc_dtls.hpp | 4 ++- 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/trunk/src/app/srs_app_rtc_conn.cpp b/trunk/src/app/srs_app_rtc_conn.cpp index ed90deecae..7038ee1f07 100644 --- a/trunk/src/app/srs_app_rtc_conn.cpp +++ b/trunk/src/app/srs_app_rtc_conn.cpp @@ -2240,11 +2240,12 @@ srs_error_t SrsRtcConnection::on_dtls_alert(std::string type, std::string desc) srs_error_t err = srs_success; // CN(Close Notify) is sent when client close the PeerConnection. - if (type == "warning" && desc == "CN") { + // fatal, IP(Illegal Parameter) is sent when DTLS failed. + if (type == "fatal" || (type == "warning" && desc == "CN")) { SrsContextRestore(_srs_context->get_id()); switch_to_context(); - srs_trace("RTC: session destroy by DTLS alert, username=%s", username_.c_str()); + srs_trace("RTC: session destroy by DTLS alert(%s %s), username=%s", type.c_str(), desc.c_str(), username_.c_str()); _srs_rtc_manager->remove(this); } diff --git a/trunk/src/app/srs_app_rtc_dtls.cpp b/trunk/src/app/srs_app_rtc_dtls.cpp index 977426b336..3bbb481a6c 100644 --- a/trunk/src/app/srs_app_rtc_dtls.cpp +++ b/trunk/src/app/srs_app_rtc_dtls.cpp @@ -50,7 +50,6 @@ unsigned int dtls_timer_cb(SSL* dtls, unsigned int previous_us) // limit the timeout in [50ms, 30s]. timeout_us = srs_max(timeout_us, 50 * 1000); timeout_us = srs_min(timeout_us, 30 * 1000 * 1000); - srs_info("DTLS: ARQ timer cb timeout=%ums, previous=%ums", timeout_us/1000, previous_us/1000); return timeout_us; @@ -395,8 +394,9 @@ SrsDtlsImpl::SrsDtlsImpl(ISrsDtlsCallback* callback) callback_ = callback; handshake_done_for_us = false; - nn_arq_packets = 0; + last_handshake_type = 0; + last_content_type = 0; version_ = SrsDtlsVersionAuto; } @@ -443,8 +443,18 @@ srs_error_t SrsDtlsImpl::write_dtls_data(void* data, int size) srs_string_dumps_hex((char*)data, size, 32).c_str()); } + // change_cipher_spec(20), alert(21), handshake(22), application_data(23) + // @see https://tools.ietf.org/html/rfc2246#section-6.2.1 + uint8_t content_type = size >= 1 ? ((uint8_t*)data)[0] : 0; + uint8_t handshake_type = size >= 14 ? ((uint8_t*)data)[13] : 0; + if (content_type && handshake_type && last_content_type == content_type && last_handshake_type == handshake_type) { + nn_arq_packets++; + } + last_content_type = content_type; + last_handshake_type = handshake_type; + // Logging when got SSL original data. - state_trace((uint8_t*)data, size, false, 0, 0, false); + state_trace((uint8_t*)data, size, false, 0); return err; } @@ -574,13 +584,17 @@ srs_error_t SrsDtlsImpl::do_on_dtls(char* data, int nb_data) // TODO: 0 or -1 maybe block, use BIO_should_retry to check. return srs_error_new(ERROR_OpenSslBIOWrite, "BIO_write r0=%d", r0); } - state_trace((uint8_t*)data, nb_data, true, r0, SSL_ERROR_NONE, false); + state_trace((uint8_t*)data, nb_data, true, r0); // If there is data available in bio_in, use SSL_read to allow SSL to process it. char buf[kRtpPacketSize]; r0 = SSL_read(dtls, buf, sizeof(buf)); int r1 = SSL_get_error(dtls, r0); ERR_clear_error(); - if (r0 > 0) { + if (r0 <= 0) { + if (r1 != SSL_ERROR_WANT_READ && r1 != SSL_ERROR_WANT_WRITE && r1 != SSL_ERROR_ZERO_RETURN) { + return srs_error_new(ERROR_RTC_DTLS, "DTLS: read r0=%d, r1=%d, done=%d", r0, r1, handshake_done_for_us); + } + } else if (r0 > 0) { srs_trace("DTLS: read r0=%d, r1=%d, padding=%d, done=%d, data=[%s]", r0, r1, BIO_ctrl_pending(bio_in), handshake_done_for_us, srs_string_dumps_hex(buf, r0, 32).c_str()); @@ -601,7 +615,7 @@ srs_error_t SrsDtlsImpl::do_on_dtls(char* data, int nb_data) return err; } -void SrsDtlsImpl::state_trace(uint8_t* data, int length, bool incoming, int r0, int r1, bool arq) +void SrsDtlsImpl::state_trace(uint8_t* data, int length, bool incoming, int r0) { // change_cipher_spec(20), alert(21), handshake(22), application_data(23) // @see https://tools.ietf.org/html/rfc2246#section-6.2.1 @@ -620,9 +634,9 @@ void SrsDtlsImpl::state_trace(uint8_t* data, int length, bool incoming, int r0, handshake_type = (uint8_t)data[13]; } - srs_trace("DTLS: State %s %s, done=%u, arq=%u/%u, r0=%d, r1=%d, len=%u, cnt=%u, size=%u, hs=%u", - (is_dtls_client()? "Active":"Passive"), (incoming? "RECV":"SEND"), handshake_done_for_us, arq, - nn_arq_packets, r0, r1, length, content_type, size, handshake_type); + srs_trace("DTLS: State %s %s, done=%u, arq=%u, r0=%d, len=%u, cnt=%u, size=%u, hs=%u", + (is_dtls_client()? "Active":"Passive"), (incoming? "RECV":"SEND"), handshake_done_for_us, + nn_arq_packets, r0, length, content_type, size, handshake_type); } const int SRTP_MASTER_KEY_KEY_LEN = 16; @@ -744,6 +758,7 @@ void SrsDtlsClientImpl::stop_arq() srs_freep(trd); } +// The timeout is set by dtls_timer_cb. srs_error_t SrsDtlsClientImpl::cycle() { srs_error_t err = srs_success; @@ -778,6 +793,12 @@ srs_error_t SrsDtlsClientImpl::cycle() // There is timeout to wait, so we should wait, because there is no packet in openssl. if (timeout > 0) { + // Sleeping for an excessively long period of time may result in a significant slowdown + // of the ARQ, even if the timeout is reset by the receipt of a packet. To ensure timely + // response, it is recommended to decrease the timeout duration and increase the frequency + // of checks. + timeout = srs_min(timeout, 100 * SRS_UTIME_MILLISECONDS); + srs_info("DTLS: ARQ wait timeout=%dms, to=%dms, r0=%d", srsu2msi(timeout), srsu2msi(to.tv_sec + to.tv_usec), r0); srs_usleep(timeout); continue; } diff --git a/trunk/src/app/srs_app_rtc_dtls.hpp b/trunk/src/app/srs_app_rtc_dtls.hpp index d5495c85cc..45839cec04 100644 --- a/trunk/src/app/srs_app_rtc_dtls.hpp +++ b/trunk/src/app/srs_app_rtc_dtls.hpp @@ -102,6 +102,8 @@ class SrsDtlsImpl bool handshake_done_for_us; // The stat for ARQ packets. int nn_arq_packets; + uint8_t last_handshake_type; + uint8_t last_content_type; public: SrsDtlsImpl(ISrsDtlsCallback* callback); virtual ~SrsDtlsImpl(); @@ -114,7 +116,7 @@ class SrsDtlsImpl virtual srs_error_t on_dtls(char* data, int nb_data); protected: srs_error_t do_on_dtls(char* data, int nb_data); - void state_trace(uint8_t* data, int length, bool incoming, int r0, int r1, bool arq); + void state_trace(uint8_t* data, int length, bool incoming, int r0); public: srs_error_t get_srtp_key(std::string& recv_key, std::string& send_key); void callback_by_ssl(std::string type, std::string desc); From 858a5fe0c5fe162ab82f56f8cb7cf56e2490e813 Mon Sep 17 00:00:00 2001 From: winlin Date: Mon, 5 Jun 2023 10:12:18 +0800 Subject: [PATCH 3/4] Refine code for comments. --- trunk/src/app/srs_app_rtc_dtls.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/trunk/src/app/srs_app_rtc_dtls.cpp b/trunk/src/app/srs_app_rtc_dtls.cpp index 3bbb481a6c..f4d68617b0 100644 --- a/trunk/src/app/srs_app_rtc_dtls.cpp +++ b/trunk/src/app/srs_app_rtc_dtls.cpp @@ -587,6 +587,10 @@ srs_error_t SrsDtlsImpl::do_on_dtls(char* data, int nb_data) state_trace((uint8_t*)data, nb_data, true, r0); // If there is data available in bio_in, use SSL_read to allow SSL to process it. + // We limit the MTU to 1200 for DTLS handshake, which ensures that the buffer is large enough for reading. + // TODO: FIXME: DTLS application messages, such as DataChannel messages, may exceed 1500 bytes, but they should be + // fragmented. This fragmentation should be done at the application level. However, I'm not certain about this + // and will leave it to the developer who is responsible for developing the DataChannel. char buf[kRtpPacketSize]; r0 = SSL_read(dtls, buf, sizeof(buf)); int r1 = SSL_get_error(dtls, r0); ERR_clear_error(); @@ -594,7 +598,7 @@ srs_error_t SrsDtlsImpl::do_on_dtls(char* data, int nb_data) if (r1 != SSL_ERROR_WANT_READ && r1 != SSL_ERROR_WANT_WRITE && r1 != SSL_ERROR_ZERO_RETURN) { return srs_error_new(ERROR_RTC_DTLS, "DTLS: read r0=%d, r1=%d, done=%d", r0, r1, handshake_done_for_us); } - } else if (r0 > 0) { + } else { srs_trace("DTLS: read r0=%d, r1=%d, padding=%d, done=%d, data=[%s]", r0, r1, BIO_ctrl_pending(bio_in), handshake_done_for_us, srs_string_dumps_hex(buf, r0, 32).c_str()); From e4eeeb4fa3332fa80cbdce39f436adf9c558b8ab Mon Sep 17 00:00:00 2001 From: winlin Date: Mon, 5 Jun 2023 10:15:06 +0800 Subject: [PATCH 4/4] Update release to v5.0.156, v6.0.47 --- trunk/doc/CHANGELOG.md | 2 ++ trunk/src/core/srs_core_version5.hpp | 2 +- trunk/src/core/srs_core_version6.hpp | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md index d0fe91aa38..294d048c5d 100644 --- a/trunk/doc/CHANGELOG.md +++ b/trunk/doc/CHANGELOG.md @@ -8,6 +8,7 @@ The changelog for SRS. ## SRS 6.0 Changelog +* v6.0, 2023-06-05, Merge [#3565](https://github.com/ossrs/srs/pull/3565): DTLS: Use bio callback to get fragment packet. v6.0.47 (#3565) * v6.0, 2023-05-29, Merge [#3513](https://github.com/ossrs/srs/pull/3513): SSL: Fix SSL_get_error get the error of other coroutine. v6.0.46 (#3513) * v6.0, 2023-05-14, Merge [#3534](https://github.com/ossrs/srs/pull/3534): Replace sprintf with snprintf to eliminate compile warnings. v6.0.45 (#3534) * v6.0, 2023-05-13, Merge [#3541](https://github.com/ossrs/srs/pull/3541): asan: Fix memory leak in asan by releasing global IPs when run_directly_or_daemon fails. v6.0.44 (#3541) @@ -60,6 +61,7 @@ The changelog for SRS. ## SRS 5.0 Changelog +* v5.0, 2023-06-05, Merge [#3565](https://github.com/ossrs/srs/pull/3565): DTLS: Use bio callback to get fragment packet. v5.0.156 (#3565) * v5.0, 2023-05-29, Merge [#3513](https://github.com/ossrs/srs/pull/3513): SSL: Fix SSL_get_error get the error of other coroutine. v5.0.155 (#3513) * v5.0, 2023-05-13, Merge [#3541](https://github.com/ossrs/srs/pull/3541): asan: Fix memory leak in asan by releasing global IPs when run_directly_or_daemon fails. v5.0.154 (#3541) * v5.0, 2023-05-12, Merge [#3539](https://github.com/ossrs/srs/pull/3539): WHIP: Improve HTTP DELETE for notifying server unpublish event. v5.0.153 (#3539) diff --git a/trunk/src/core/srs_core_version5.hpp b/trunk/src/core/srs_core_version5.hpp index fe7b6beebd..982777dba4 100644 --- a/trunk/src/core/srs_core_version5.hpp +++ b/trunk/src/core/srs_core_version5.hpp @@ -9,6 +9,6 @@ #define VERSION_MAJOR 5 #define VERSION_MINOR 0 -#define VERSION_REVISION 155 +#define VERSION_REVISION 156 #endif diff --git a/trunk/src/core/srs_core_version6.hpp b/trunk/src/core/srs_core_version6.hpp index b1fdb167af..4f6b719fb1 100644 --- a/trunk/src/core/srs_core_version6.hpp +++ b/trunk/src/core/srs_core_version6.hpp @@ -9,6 +9,6 @@ #define VERSION_MAJOR 6 #define VERSION_MINOR 0 -#define VERSION_REVISION 46 +#define VERSION_REVISION 47 #endif