From 6becadd5a1e600f13c8f707349e3a8b97312650f Mon Sep 17 00:00:00 2001 From: winlin Date: Fri, 2 Jun 2023 19:00:36 +0800 Subject: [PATCH] DTLS: Use bio callback to get fragment packet. --- trunk/src/app/srs_app_rtc_dtls.cpp | 107 +++++++++++++++-------------- trunk/src/app/srs_app_rtc_dtls.hpp | 3 + 2 files changed, 59 insertions(+), 51 deletions(-) diff --git a/trunk/src/app/srs_app_rtc_dtls.cpp b/trunk/src/app/srs_app_rtc_dtls.cpp index 38f862d899..1c14b2a13f 100644 --- a/trunk/src/app/srs_app_rtc_dtls.cpp +++ b/trunk/src/app/srs_app_rtc_dtls.cpp @@ -425,6 +425,40 @@ 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; + + // Callback for the final output data, before send-out. + if ((err = on_final_out_data((uint8_t*)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()); + } + + // 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 +480,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 +501,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,6 +511,21 @@ 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; @@ -500,16 +553,8 @@ srs_error_t SrsDtlsImpl::do_on_dtls(char* data, int nb_data) srs_info("DTLS: After done, got %d bytes", nb_data); } - 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. + int r0 = 0; state_trace((uint8_t*)data, nb_data, true, r0, SSL_ERROR_NONE, false); if ((r0 = BIO_write(bio_in, data, nb_data)) <= 0) { @@ -590,26 +635,6 @@ srs_error_t SrsDtlsImpl::do_handshake() // OK, Handshake is done, note that it maybe done many times. if (r1 == SSL_ERROR_NONE) { 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"); } @@ -709,7 +734,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; } @@ -863,36 +887,17 @@ srs_error_t SrsDtlsClientImpl::cycle() 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(); + r0 = DTLSv1_handle_timeout(dtls); int r1 = SSL_get_error(dtls, r0); ERR_clear_error(); if (r0 == 0) { continue; // No timeout had expired. } if (r0 != 1) { 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; diff --git a/trunk/src/app/srs_app_rtc_dtls.hpp b/trunk/src/app/srs_app_rtc_dtls.hpp index 6daf7f5c77..4ebef699c5 100644 --- a/trunk/src/app/srs_app_rtc_dtls.hpp +++ b/trunk/src/app/srs_app_rtc_dtls.hpp @@ -109,6 +109,9 @@ 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;