mediaSoup源码分析-dtls操作

dtls主要用来交换srtp的

#在ICE完成后,开始dtls过程


	void WebRtcTransport::MayRunDtlsTransport()
	{
		MS_TRACE();

		// Do nothing if we have the same local DTLS role as the DTLS transport.
		// NOTE: local role in DTLS transport can be NONE, but not ours.
		if (this->dtlsTransport->GetLocalRole() == this->dtlsRole)
			return;

		// Check our local DTLS role.
		switch (this->dtlsRole)
		{
			// If still 'auto' then transition to 'server' if ICE is 'connected' or
			// 'completed'.
			case RTC::DtlsTransport::Role::AUTO:
			{
				if (
				  this->iceServer->GetState() == RTC::IceServer::IceState::CONNECTED ||
				  this->iceServer->GetState() == RTC::IceServer::IceState::COMPLETED)
				{
					MS_DEBUG_TAG(
					  dtls, "transition from DTLS local role 'auto' to 'server' and running DTLS transport");

					this->dtlsRole = RTC::DtlsTransport::Role::SERVER;
					this->dtlsTransport->Run(RTC::DtlsTransport::Role::SERVER);
				}

				break;
			}

			// 'client' is only set if a 'connect' request was previously called with
			// remote DTLS role 'server'.
			//
			// If 'client' then wait for ICE to be 'completed' (got USE-CANDIDATE).
			//
			// NOTE: This is the theory, however let's be more flexible as told here:
			//   https://bugs.chromium.org/p/webrtc/issues/detail?id=3661
			case RTC::DtlsTransport::Role::CLIENT:
			{
				if (
				  this->iceServer->GetState() == RTC::IceServer::IceState::CONNECTED ||
				  this->iceServer->GetState() == RTC::IceServer::IceState::COMPLETED)
				{
					MS_DEBUG_TAG(dtls, "running DTLS transport in local role 'client'");

					this->dtlsTransport->Run(RTC::DtlsTransport::Role::CLIENT);
				}

				break;
			}

			// If 'server' then run the DTLS transport if ICE is 'connected' (not yet
			// USE-CANDIDATE) or 'completed'.
			case RTC::DtlsTransport::Role::SERVER:
			{
				if (
				  this->iceServer->GetState() == RTC::IceServer::IceState::CONNECTED ||
				  this->iceServer->GetState() == RTC::IceServer::IceState::COMPLETED)
				{
					MS_DEBUG_TAG(dtls, "running DTLS transport in local role 'server'");

					this->dtlsTransport->Run(RTC::DtlsTransport::Role::SERVER);
				}

				break;
			}

			case RTC::DtlsTransport::Role::NONE:
			{
				MS_ABORT("local DTLS role not set");
			}
		}
	}

#dtls过程


	void DtlsTransport::Run(Role localRole)
	{
		MS_TRACE();

		MS_ASSERT(
		  localRole == Role::CLIENT || localRole == Role::SERVER,
		  "local DTLS role must be 'client' or 'server'");

		Role previousLocalRole = this->localRole;

		if (localRole == previousLocalRole)
		{
			MS_ERROR("same local DTLS role provided, doing nothing");

			return;
		}

		// If the previous local DTLS role was 'client' or 'server' do reset.
		if (previousLocalRole == Role::CLIENT || previousLocalRole == Role::SERVER)
		{
			MS_DEBUG_TAG(dtls, "resetting DTLS due to local role change");

			Reset();
		}

		// Update local role.
		this->localRole = localRole;

		// Set state and notify the listener.
		this->state = DtlsState::CONNECTING;
		this->listener->OnDtlsTransportConnecting(this);

		switch (this->localRole)
		{
			case Role::CLIENT:
			{
				MS_DEBUG_TAG(dtls, "running [role:client]");
				//设置主动连接
				SSL_set_connect_state(this->ssl);
				//开始握手
				SSL_do_handshake(this->ssl);
				//发送数据包
				SendPendingOutgoingDtlsData();
				//设置超时时间
				SetTimeout();

				break;
			}

			case Role::SERVER:
			{
				MS_DEBUG_TAG(dtls, "running [role:server]");
				//等待握手
				SSL_set_accept_state(this->ssl);
				//握手
				SSL_do_handshake(this->ssl);

				break;
			}

			default:
			{
				MS_ABORT("invalid local DTLS role");
			}
		}
	}

#dtls包处理


	void DtlsTransport::ProcessDtlsData(const uint8_t* data, size_t len)
	{
		MS_TRACE();

		int written;
		int read;

		if (!IsRunning())
		{
			MS_ERROR("cannot process data while not running");

			return;
		}

		// Write the received DTLS data into the sslBioFromNetwork.
		written = BIO_write(this->sslBioFromNetwork, (const void*)data, static_cast(len));

		if (written != static_cast(len))
		{
			MS_WARN_TAG(
			  dtls,
			  "OpenSSL BIO_write() wrote less (%zu bytes) than given data (%zu bytes)",
			  static_cast(written),
			  len);
		}

		// Must call SSL_read() to process received DTLS data.
		read = SSL_read(this->ssl, (void*)DtlsTransport::sslReadBuffer, SslReadBufferSize);

		// Send data if it's ready.
		SendPendingOutgoingDtlsData();

		// Check SSL status and return if it is bad/closed.
		if (!CheckStatus(read))
			return;

		// Set/update the DTLS timeout.
		if (!SetTimeout())
			return;

		// Application data received. Notify to the listener.
		if (read > 0)
		{
			// It is allowed to receive DTLS data even before validating remote fingerprint.
			if (!this->handshakeDone)
			{
				MS_WARN_TAG(dtls, "ignoring application data received while DTLS handshake not done");

				return;
			}

			// Notify the listener.
			this->listener->OnDtlsTransportApplicationDataReceived(
			  this, (uint8_t*)DtlsTransport::sslReadBuffer, static_cast(read));
		}
	}

#什么时候handshake完毕?


	inline void DtlsTransport::OnSslInfo(int where, int ret)
	{
		MS_TRACE();

		int w = where & -SSL_ST_MASK;
		const char* role;

		if ((w & SSL_ST_CONNECT) != 0)
			role = "client";
		else if ((w & SSL_ST_ACCEPT) != 0)
			role = "server";
		else
			role = "undefined";

		if ((where & SSL_CB_LOOP) != 0)
		{
			MS_DEBUG_TAG(dtls, "[role:%s, action:'%s']", role, SSL_state_string_long(this->ssl));
		}
		else if ((where & SSL_CB_ALERT) != 0)
		{
			const char* alertType;

			switch (*SSL_alert_type_string(ret))
			{
				case 'W':
					alertType = "warning";
					break;

				case 'F':
					alertType = "fatal";
					break;

				default:
					alertType = "undefined";
			}

			if ((where & SSL_CB_READ) != 0)
			{
				MS_WARN_TAG(dtls, "received DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret));
			}
			else if ((where & SSL_CB_WRITE) != 0)
			{
				MS_DEBUG_TAG(dtls, "sending DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret));
			}
			else
			{
				MS_DEBUG_TAG(dtls, "DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret));
			}
		}
		else if ((where & SSL_CB_EXIT) != 0)
		{
			if (ret == 0)
				MS_DEBUG_TAG(dtls, "[role:%s, failed:'%s']", role, SSL_state_string_long(this->ssl));
			else if (ret < 0)
				MS_DEBUG_TAG(dtls, "role: %s, waiting:'%s']", role, SSL_state_string_long(this->ssl));
		}
		else if ((where & SSL_CB_HANDSHAKE_START) != 0)
		{
			MS_DEBUG_TAG(dtls, "DTLS handshake start");
		}
		else if ((where & SSL_CB_HANDSHAKE_DONE) != 0)
		{
			MS_DEBUG_TAG(dtls, "DTLS handshake done");

			this->handshakeDoneNow = true;
		}

		// NOTE: checking SSL_get_shutdown(this->ssl) & SSL_RECEIVED_SHUTDOWN here upon
		// receipt of a close alert does not work (the flag is set after this callback).
	}

可以看到是一个回调,在这里设置的

		// Set SSL info callback.
		SSL_CTX_set_info_callback(DtlsTransport::sslCtx, onSslInfo);

#dtls如何初始化?


	void DtlsTransport::ClassInit()
	{
		MS_TRACE();

		// Generate a X509 certificate and private key (unless PEM files are provided).
		if (
		  Settings::configuration.dtlsCertificateFile.empty() ||
		  Settings::configuration.dtlsPrivateKeyFile.empty())
		{
			GenerateCertificateAndPrivateKey();
		}
		else
		{
			ReadCertificateAndPrivateKeyFromFiles();
		}

		// Create a global SSL_CTX.
		CreateSslCtx();

		// Generate certificate fingerprints.
		GenerateFingerprints();
	}

读取证书和私钥


	void DtlsTransport::ReadCertificateAndPrivateKeyFromFiles()
	{
		MS_TRACE();

		FILE* file{ nullptr };

		file = fopen(Settings::configuration.dtlsCertificateFile.c_str(), "r");

		if (file == nullptr)
		{
			MS_ERROR("error reading DTLS certificate file: %s", std::strerror(errno));
			goto error;
		}

		DtlsTransport::certificate = PEM_read_X509(file, nullptr, nullptr, nullptr);

		if (DtlsTransport::certificate == nullptr)
		{
			LOG_OPENSSL_ERROR("PEM_read_X509() failed");
			goto error;
		}

		fclose(file);

		file = fopen(Settings::configuration.dtlsPrivateKeyFile.c_str(), "r");

		if (file == nullptr)
		{
			MS_ERROR("error reading DTLS private key file: %s", std::strerror(errno));
			goto error;
		}

		DtlsTransport::privateKey = PEM_read_PrivateKey(file, nullptr, nullptr, nullptr);

		if (DtlsTransport::privateKey == nullptr)
		{
			LOG_OPENSSL_ERROR("PEM_read_PrivateKey() failed");
			goto error;
		}

		fclose(file);

		return;

	error:

		MS_THROW_ERROR("error reading DTLS certificate and private key PEM files");
	}

创建ssl_ctx和指纹


	void DtlsTransport::CreateSslCtx()
	{
		MS_TRACE();

		std::string dtlsSrtpProfiles;
		EC_KEY* ecdh{ nullptr };
		int ret;

/* Set the global DTLS context. */

// Both DTLS 1.0 and 1.2 (requires OpenSSL >= 1.1.0).
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
		DtlsTransport::sslCtx = SSL_CTX_new(DTLS_method());
// Just DTLS 1.0 (requires OpenSSL >= 1.0.1).
#elif (OPENSSL_VERSION_NUMBER >= 0x10001000L)
		DtlsTransport::sslCtx = SSL_CTX_new(DTLSv1_method());
#else
#error "too old OpenSSL version"
#endif

		if (DtlsTransport::sslCtx == nullptr)
		{
			LOG_OPENSSL_ERROR("SSL_CTX_new() failed");
			goto error;
		}

		ret = SSL_CTX_use_certificate(DtlsTransport::sslCtx, DtlsTransport::certificate);

		if (ret == 0)
		{
			LOG_OPENSSL_ERROR("SSL_CTX_use_certificate() failed");
			goto error;
		}

		ret = SSL_CTX_use_PrivateKey(DtlsTransport::sslCtx, DtlsTransport::privateKey);

		if (ret == 0)
		{
			LOG_OPENSSL_ERROR("SSL_CTX_use_PrivateKey() failed");
			goto error;
		}

		ret = SSL_CTX_check_private_key(DtlsTransport::sslCtx);

		if (ret == 0)
		{
			LOG_OPENSSL_ERROR("SSL_CTX_check_private_key() failed");
			goto error;
		}

		// Set options.
		SSL_CTX_set_options(
		  DtlsTransport::sslCtx,
		  SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_TICKET | SSL_OP_SINGLE_ECDH_USE |
		    SSL_OP_NO_QUERY_MTU);

		// Don't use sessions cache.
		SSL_CTX_set_session_cache_mode(DtlsTransport::sslCtx, SSL_SESS_CACHE_OFF);

		// Read always as much into the buffer as possible.
		// NOTE: This is the default for DTLS, but a bug in non latest OpenSSL
		// versions makes this call required.
		SSL_CTX_set_read_ahead(DtlsTransport::sslCtx, 1);

		SSL_CTX_set_verify_depth(DtlsTransport::sslCtx, 4);

		// Require certificate from peer.
		SSL_CTX_set_verify(
		  DtlsTransport::sslCtx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, onSslCertificateVerify);

		// Set SSL info callback.
		SSL_CTX_set_info_callback(DtlsTransport::sslCtx, onSslInfo);

		// Set ciphers.
		ret = SSL_CTX_set_cipher_list(
		  DtlsTransport::sslCtx, "ALL:!ADH:!LOW:!EXP:!MD5:!aNULL:!eNULL:@STRENGTH");

		if (ret == 0)
		{
			LOG_OPENSSL_ERROR("SSL_CTX_set_cipher_list() failed");
			goto error;
		}

// Enable ECDH ciphers.
// DOC: http://en.wikibooks.org/wiki/OpenSSL/Diffie-Hellman_parameters
// NOTE: https://code.google.com/p/chromium/issues/detail?id=406458
// NOTE: https://bugs.ruby-lang.org/issues/12324
//
// Nothing to be done in OpenSSL >= 1.1.0.
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
// For OpenSSL >= 1.0.2.
#elif (OPENSSL_VERSION_NUMBER >= 0x10002000L)
		SSL_CTX_set_ecdh_auto(DtlsTransport::sslCtx, 1);
// Older versions.
#else
		ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);

		if (!ecdh)
		{
			LOG_OPENSSL_ERROR("EC_KEY_new_by_curve_name() failed");
			goto error;
		}

		if (SSL_CTX_set_tmp_ecdh(DtlsTransport::sslCtx, ecdh) != 1)
		{
			LOG_OPENSSL_ERROR("SSL_CTX_set_tmp_ecdh() failed");
			goto error;
		}

		EC_KEY_free(ecdh);
		ecdh = nullptr;
#endif

		// Set the "use_srtp" DTLS extension.
		for (auto it = DtlsTransport::srtpProfiles.begin(); it != DtlsTransport::srtpProfiles.end(); ++it)
		{
			if (it != DtlsTransport::srtpProfiles.begin())
				dtlsSrtpProfiles += ":";

			SrtpProfileMapEntry* profileEntry = std::addressof(*it);
			dtlsSrtpProfiles += profileEntry->name;
		}

		MS_DEBUG_2TAGS(dtls, srtp, "setting SRTP profiles for DTLS: %s", dtlsSrtpProfiles.c_str());

		// NOTE: This function returns 0 on success.
		ret = SSL_CTX_set_tlsext_use_srtp(DtlsTransport::sslCtx, dtlsSrtpProfiles.c_str());

		if (ret != 0)
		{
			MS_ERROR("SSL_CTX_set_tlsext_use_srtp() failed when entering '%s'", dtlsSrtpProfiles.c_str());
			LOG_OPENSSL_ERROR("SSL_CTX_set_tlsext_use_srtp() failed");
			goto error;
		}

		return;

	error:

		if (DtlsTransport::sslCtx != nullptr)
		{
			SSL_CTX_free(DtlsTransport::sslCtx);
			DtlsTransport::sslCtx = nullptr;
		}

		if (ecdh != nullptr)
			EC_KEY_free(ecdh);

		MS_THROW_ERROR("SSL context creation failed");
	}

	void DtlsTransport::GenerateFingerprints()
	{
		MS_TRACE();

		for (auto& kv : DtlsTransport::string2FingerprintAlgorithm)
		{
			const std::string& algorithmString = kv.first;
			FingerprintAlgorithm algorithm     = kv.second;
			uint8_t binaryFingerprint[EVP_MAX_MD_SIZE];
			unsigned int size{ 0 };
			char hexFingerprint[(EVP_MAX_MD_SIZE * 3) + 1];
			const EVP_MD* hashFunction;
			int ret;

			switch (algorithm)
			{
				case FingerprintAlgorithm::SHA1:
					hashFunction = EVP_sha1();
					break;

				case FingerprintAlgorithm::SHA224:
					hashFunction = EVP_sha224();
					break;

				case FingerprintAlgorithm::SHA256:
					hashFunction = EVP_sha256();
					break;

				case FingerprintAlgorithm::SHA384:
					hashFunction = EVP_sha384();
					break;

				case FingerprintAlgorithm::SHA512:
					hashFunction = EVP_sha512();
					break;

				default:
					MS_THROW_ERROR("unknown algorithm");
			}

			ret = X509_digest(DtlsTransport::certificate, hashFunction, binaryFingerprint, &size);

			if (ret == 0)
			{
				MS_ERROR("X509_digest() failed");
				MS_THROW_ERROR("Fingerprints generation failed");
			}

			// Convert to hexadecimal format in uppercase with colons.
			for (unsigned int i{ 0 }; i < size; ++i)
			{
				std::sprintf(hexFingerprint + (i * 3), "%.2X:", binaryFingerprint[i]);
			}
			hexFingerprint[(size * 3) - 1] = '\0';

			MS_DEBUG_TAG(dtls, "%-7s fingerprint: %s", algorithmString.c_str(), hexFingerprint);

			// Store it in the vector.
			DtlsTransport::Fingerprint fingerprint;

			fingerprint.algorithm = DtlsTransport::GetFingerprintAlgorithm(algorithmString);
			fingerprint.value     = hexFingerprint;

			DtlsTransport::localFingerprints.push_back(fingerprint);
		}
	}

#最后dtls handshake完了后交换的数据如下


	inline void DtlsTransport::ExtractSrtpKeys(RTC::SrtpSession::Profile srtpProfile)
	{
		MS_TRACE();

		size_t srtpKeyLength{ 0 };
		size_t srtpSaltLength{ 0 };
		size_t srtpMasterLength{ 0 };

		switch (srtpProfile)
		{
			case RTC::SrtpSession::Profile::AES_CM_128_HMAC_SHA1_80:
			case RTC::SrtpSession::Profile::AES_CM_128_HMAC_SHA1_32:
			{
				srtpKeyLength    = SrtpMasterKeyLength;
				srtpSaltLength   = SrtpMasterSaltLength;
				srtpMasterLength = SrtpMasterLength;

				break;
			}

			case RTC::SrtpSession::Profile::AEAD_AES_256_GCM:
			{
				srtpKeyLength    = SrtpAesGcm256MasterKeyLength;
				srtpSaltLength   = SrtpAesGcm256MasterSaltLength;
				srtpMasterLength = SrtpAesGcm256MasterLength;

				break;
			}

			case RTC::SrtpSession::Profile::AEAD_AES_128_GCM:
			{
				srtpKeyLength    = SrtpAesGcm128MasterKeyLength;
				srtpSaltLength   = SrtpAesGcm128MasterSaltLength;
				srtpMasterLength = SrtpAesGcm128MasterLength;

				break;
			}

			default:
			{
				MS_ABORT("unknown SRTP profile");
			}
		}

		uint8_t srtpMaterial[srtpMasterLength * 2];
		uint8_t* srtpLocalKey;
		uint8_t* srtpLocalSalt;
		uint8_t* srtpRemoteKey;
		uint8_t* srtpRemoteSalt;
		uint8_t srtpLocalMasterKey[srtpMasterLength];
		uint8_t srtpRemoteMasterKey[srtpMasterLength];
		int ret;

		ret = SSL_export_keying_material(
		  this->ssl, srtpMaterial, srtpMasterLength * 2, "EXTRACTOR-dtls_srtp", 19, nullptr, 0, 0);

		MS_ASSERT(ret != 0, "SSL_export_keying_material() failed");

		switch (this->localRole)
		{
			case Role::SERVER:
			{
				srtpRemoteKey  = srtpMaterial;
				srtpLocalKey   = srtpRemoteKey + srtpKeyLength;
				srtpRemoteSalt = srtpLocalKey + srtpKeyLength;
				srtpLocalSalt  = srtpRemoteSalt + srtpSaltLength;

				break;
			}

			case Role::CLIENT:
			{
				srtpLocalKey   = srtpMaterial;
				srtpRemoteKey  = srtpLocalKey + srtpKeyLength;
				srtpLocalSalt  = srtpRemoteKey + srtpKeyLength;
				srtpRemoteSalt = srtpLocalSalt + srtpSaltLength;

				break;
			}

			default:
			{
				MS_ABORT("no DTLS role set");
			}
		}

		// Create the SRTP local master key.
		std::memcpy(srtpLocalMasterKey, srtpLocalKey, srtpKeyLength);
		std::memcpy(srtpLocalMasterKey + srtpKeyLength, srtpLocalSalt, srtpSaltLength);
		// Create the SRTP remote master key.
		std::memcpy(srtpRemoteMasterKey, srtpRemoteKey, srtpKeyLength);
		std::memcpy(srtpRemoteMasterKey + srtpKeyLength, srtpRemoteSalt, srtpSaltLength);

		// Set state and notify the listener.
		this->state = DtlsState::CONNECTED;
		this->listener->OnDtlsTransportConnected(
		  this,
		  srtpProfile,
		  srtpLocalMasterKey,
		  srtpMasterLength,
		  srtpRemoteMasterKey,
		  srtpMasterLength,
		  this->remoteCert);
	}

 

你可能感兴趣的:(音视频处理)