HistogramDtlsVersion ToHistogramDtlsVersion(int version_bytes) { switch (version_bytes) { case kDtls10VersionBytes: return HistogramDtlsVersion::kDtls10; case kDtls12VersionBytes: return HistogramDtlsVersion::kDtls12; case kDtls13VersionBytes: return HistogramDtlsVersion::kDtls13; default: return HistogramDtlsVersion::kUnknown;
}
}
} // namespace
template <typename Sink> void AbslStringify(Sink& sink, DtlsTransportState state) { switch (state) { case DtlsTransportState::kNew:
sink.Append("kNew"); break; case DtlsTransportState::kConnecting:
sink.Append("kConnecting"); break; case DtlsTransportState::kConnected:
sink.Append("kConnected"); break; case DtlsTransportState::kClosed:
sink.Append("kClosed"); break; case DtlsTransportState::kFailed:
sink.Append("kFailed"); break; case DtlsTransportState::kNumValues:
sink.Append("kNumValues"); break;
}
}
// We don't pull the RTP constants from rtputils.h, to avoid a layer violation.
constexpr size_t kMinRtpPacketLen = 12;
// Maximum number of pending packets in the queue. Packets are read immediately // after they have been written, so a capacity of "1" is sufficient. // // However, this bug seems to indicate that's not the case: crbug.com/1063834 // So, temporarily increasing it to 2 to see if that makes a difference.
constexpr size_t kMaxPendingPackets = 2;
// Minimum and maximum values for the initial DTLS handshake timeout. We'll pick // an initial timeout based on ICE RTT estimates, but clamp it to this range.
constexpr int kMinDtlsHandshakeTimeoutMs = 50;
constexpr int kMaxDtlsHandshakeTimeoutMs = 3000; // This effectively disables the handshake timeout.
constexpr int kDisabledHandshakeTimeoutMs = 3600 * 1000 * 24;
AsyncSocketPacketOptions packet_options; if (next_packet_options_) {
packet_options = std::move(*next_packet_options_);
next_packet_options_.reset();
} elseif (dtls_stun_piggyback_controller_) { // Note: packets with `next_packet_options_` are user packets. // Packets without `next_packet_options_` are generated by BoringSSL. // The DtlsStunPiggybackController captures BoringSSL packets.
RTC_DCHECK(IsDtlsPacket(data));
dtls_stun_piggyback_controller_->CapturePacket(data);
}
ice_transport_->SendPacket(reinterpret_cast<constchar*>(data.data()),
data.size(), packet_options);
written = data.size(); return SR_SUCCESS;
}
if (dtls_stun_piggyback_controller_) {
dtls_stun_piggyback_controller_->Flush();
} returnfalse;
}
bool StreamInterfaceChannel::OnPacketReceived(std::span<const uint8_t> data) {
RTC_DCHECK_RUN_ON(&callback_sequence_); if (packets_.size() > 0) {
RTC_LOG(LS_WARNING) << "Packet already in queue.";
} bool ret = packets_.WriteBack(reinterpret_cast<constchar*>(data.data()),
data.size(), nullptr); if (!ret) { // Somehow we received another packet before the SSLStreamAdapter read the // previous one out of our temporary buffer. In this case, we'll log an // error and still signal the read event, hoping that it will read the // packet currently in packets_.
RTC_LOG(LS_ERROR) << "Failed to write packet to queue.";
} // If we use DTLS-in-STUN, the controller should be informed about incoming // packets so it can acknowledge them. Note that this packet may have been // emitted by the controller. if (dtls_stun_piggyback_controller_) {
dtls_stun_piggyback_controller_->ReportDtlsPacket(data);
}
FireEvent(SE_READ, 0); return ret;
}
bool DtlsTransportInternalImpl::SetDtlsRole(SSLRole role) { if (dtls_) {
RTC_DCHECK(dtls_role_); if (*dtls_role_ != role) {
RTC_LOG(LS_ERROR)
<< "SSL Role can't be reversed after the session is setup."; returnfalse;
} return true;
}
// Once we have the local certificate, the same remote fingerprint can be set // multiple times. if (dtls_active_ && remote_fingerprint_value_ == remote_fingerprint_value &&
!digest_alg.empty()) { // This may happen during renegotiation.
RTC_LOG(LS_INFO) << ToString()
<< ": Ignoring identical remote DTLS fingerprint"; return true;
}
// If the other side doesn't support DTLS, turn off `dtls_active_`. // TODO(deadbeef): Remove this. It's dangerous, because it relies on higher // level code to ensure DTLS is actually used, but there are tests that // depend on it, for the case where an m= section is rejected. In that case // SetRemoteFingerprint shouldn't even be called though. if (digest_alg.empty()) {
RTC_DCHECK(!digest_len);
RTC_LOG(LS_INFO) << ToString() << ": Other side didn't support DTLS.";
dtls_active_ = false; return true;
}
// Otherwise, we must have a local certificate before setting remote // fingerprint. if (!dtls_active_) {
RTC_LOG(LS_ERROR) << ToString()
<< ": Can't set DTLS remote settings in this state."; returnfalse;
}
// At this point we know we are doing DTLS bool fingerprint_changing = !remote_fingerprint_value_.empty();
remote_fingerprint_value_ = std::move(remote_fingerprint_value);
remote_fingerprint_algorithm_ = std::string(digest_alg);
if (dtls_ && !fingerprint_changing) { // This can occur if DTLS is set up before a remote fingerprint is // received. For instance, if we set up DTLS due to receiving an early // ClientHello.
SSLPeerCertificateDigestError err = dtls_->SetPeerCertificateDigest(
remote_fingerprint_algorithm_, remote_fingerprint_value_); if (err != SSLPeerCertificateDigestError::NONE) {
RTC_LOG(LS_ERROR) << ToString()
<< ": Couldn't set DTLS certificate digest.";
set_dtls_state(DtlsTransportState::kFailed); // If the error is "verification failed", don't return false, because // this means the fingerprint was formatted correctly but didn't match // the certificate from the DTLS handshake. Thus the DTLS state should go // to "failed", but SetRemoteDescription shouldn't fail. return err == SSLPeerCertificateDigestError::VERIFICATION_FAILED;
} return true;
}
// If the fingerprint is changing, we'll tear down the DTLS association and // create a new one, resetting our state. if (dtls_ && fingerprint_changing) {
dtls_.reset(nullptr);
set_dtls_state(DtlsTransportState::kNew);
set_writable(false);
}
if (!SetupDtls()) {
set_dtls_state(DtlsTransportState::kFailed); returnfalse;
}
return true;
}
std::unique_ptr<SSLCertChain> DtlsTransportInternalImpl::GetRemoteSSLCertChain() const { if (!dtls_) { return nullptr;
}
// TODO(jonaso,webrtc:367395350): Add more clever handling of MTU // (such as automatic packetization smoothing). if (dtls_in_stun_) { // - This is only needed when using PQC but we don't know that here. // - 900 is sufficiently small so that dtls pqc handshake packets // can get put into STUN attributes and still fit into two packets. constint kDtlsMtu = 900;
dtls_->SetMTU(kDtlsMtu);
}
if (fake_ice_lite_) { int rtt_ms = kDefaultHandshakeEstimateRttMs; int initial_timeout_ms = ComputeRetransmissionTimeout(rtt_ms);
dtls_->SetInitialRetransmissionTimeout(initial_timeout_ms);
}
// Set up DTLS-SRTP, if it's been enabled. if (!srtp_ciphers_.empty()) { if (!dtls_->SetDtlsSrtpCryptoSuites(srtp_ciphers_)) {
RTC_LOG(LS_ERROR) << ToString() << ": Couldn't set DTLS-SRTP ciphers."; returnfalse;
}
} else {
RTC_LOG(LS_INFO) << ToString() << ": Not using DTLS-SRTP.";
}
if (!dtls_->SetSslGroupIds(ephemeral_key_exchange_cipher_groups_)) {
RTC_LOG(LS_ERROR) << ToString() << ": Couldn't set DTLS SSL Group Ids."; returnfalse;
}
// Called from upper layers to send a media packet. int DtlsTransportInternalImpl::SendPacket( constchar* data,
size_t size, const AsyncSocketPacketOptions& options, int flags) { if (!dtls_active_) { // Not doing DTLS. return ice_transport()->SendPacket(data, size, options);
}
switch (dtls_state()) { case DtlsTransportState::kNew: // Can't send data until the connection is active. // TODO(ekr@rtfm.com): assert here if dtls_ is NULL? return -1; case DtlsTransportState::kConnecting: // Can't send data until the connection is active. return -1; case DtlsTransportState::kConnected: if (flags & PF_SRTP_BYPASS) {
RTC_DCHECK(!srtp_ciphers_.empty()); if (!IsRtpPacket(
std::span(reinterpret_cast<const uint8_t*>(data), size))) { return -1;
}
return ice_transport()->SendPacket(data, size, options);
} else {
downward_->SetNextPacketOptions(options);
size_t written; int error; // TODO(jonaso): Change the dtls_ interface so that it instead returns // an encrypted packet, rather than calling the // StreamInterfaceChannel::Write function. Such change would remove the // need of the next_packet_options_.
StreamResult result = dtls_->Write(
std::span(reinterpret_cast<const uint8_t*>(data), size), written,
error); if (result != SR_SUCCESS) { // Explicitly clear the next packet options, in case no packet was // sent.
downward_->ClearNextPacketOptions(); return -1;
} // For DTLS, a SSL_Write operation will either send the entire data in a // single record, or fail the entire send. See for example the // documentation on SSL_write in boringssl/src/include/openssl/ssl.h
RTC_CHECK(written == size); return static_cast<int>(size);
} case DtlsTransportState::kFailed: // Can't send anything when we're failed.
RTC_LOG(LS_ERROR) << ToString()
<< ": Couldn't send packet due to " "DtlsTransportState::kFailed."; return -1; case DtlsTransportState::kClosed: // Can't send anything when we're closed.
RTC_LOG(LS_ERROR) << ToString()
<< ": Couldn't send packet due to " "DtlsTransportState::kClosed."; return -1; default:
RTC_DCHECK_NOTREACHED(); return -1;
}
}
// The state transition logic here is as follows: // (1) If we're not doing DTLS-SRTP, then the state is just the // state of the underlying impl() // (2) If we're doing DTLS-SRTP: // - Prior to the DTLS handshake, the state is neither receiving nor // writable // - When the impl goes writable for the first time we // start the DTLS handshake // - Once the DTLS handshake completes, the state is that of the // impl again void DtlsTransportInternalImpl::OnWritableState(
PacketTransportInternal* transport) {
RTC_DCHECK_RUN_ON(&thread_checker_);
RTC_DCHECK(transport == ice_transport());
RTC_LOG(LS_INFO) << ToString() << ": ice_transport writable state changed to "
<< ice_transport()->writable()
<< " dtls_state: " << dtls_state();
if (!ice_has_been_writable_) { // Ice starts as not writable. The first time this method is called, it // should be when ice change to writable = true.
RTC_DCHECK(ice_transport()->writable());
} bool first_ice_writable = !ice_has_been_writable_;
ice_has_been_writable_ = true;
if (!dtls_active_) { // Not doing DTLS. // Note: SignalWritableState fired by set_writable.
set_writable(ice_transport()->writable()); return;
}
switch (dtls_state()) { case DtlsTransportState::kNew:
MaybeStartDtls(); break; case DtlsTransportState::kConnected: // Note: SignalWritableState fired by set_writable. if (dtls_in_stun_ && dtls_ && first_ice_writable) { // Dtls1.3 has one remaining packet after it has become kConnected (?), // make sure that this packet is sent too.
UpdateHandshakeTimeout();
PeriodicRetransmitDtlsPacketUntilDtlsConnected();
}
set_writable(ice_transport()->writable()); break; case DtlsTransportState::kConnecting: if (dtls_in_stun_ && dtls_) { // If DTLS piggybacking is enabled, we set the timeout // on the DTLS object (which is then different from the // inital kDisabledHandshakeTimeoutMs)
UpdateHandshakeTimeout();
PeriodicRetransmitDtlsPacketUntilDtlsConnected();
} break; case DtlsTransportState::kFailed: // Should not happen. Do nothing.
RTC_LOG(LS_ERROR) << ToString()
<< ": OnWritableState() called in state " "DtlsTransportState::kFailed."; break; case DtlsTransportState::kClosed: // Should not happen. Do nothing.
RTC_LOG(LS_ERROR) << ToString()
<< ": OnWritableState() called in state " "DtlsTransportState::kClosed."; break; case DtlsTransportState::kNumValues:
RTC_DCHECK_NOTREACHED(); break;
}
}
void DtlsTransportInternalImpl::OnReceivingState(
PacketTransportInternal* transport) {
RTC_DCHECK_RUN_ON(&thread_checker_);
RTC_DCHECK(transport == ice_transport());
RTC_LOG(LS_VERBOSE) << ToString()
<< ": ice_transport " "receiving state changed to "
<< ice_transport()->receiving(); if (!dtls_active_ || dtls_state() == DtlsTransportState::kConnected) { // Note: SignalReceivingState fired by set_receiving.
set_receiving(ice_transport()->receiving());
}
}
if (!dtls_active_) { // Not doing DTLS.
NotifyPacketReceived(packet); return;
}
switch (dtls_state()) { case DtlsTransportState::kNew: if (dtls_) {
RTC_LOG(LS_INFO) << ToString()
<< ": Packet received before DTLS started.";
} else {
RTC_LOG(LS_WARNING) << ToString()
<< ": Packet received before we know if we are " "doing DTLS or not.";
} // Cache a client hello packet received before DTLS has actually started. if (IsDtlsClientHelloPacket(packet.payload())) {
RTC_LOG(LS_INFO) << ToString()
<< ": Caching DTLS ClientHello packet until DTLS is " "started.";
cached_client_hello_.AddIfUnique(packet.payload());
cached_client_hello_.Prune(kMaxCachedClientHello); // If we haven't started setting up DTLS yet (because we don't have a // remote fingerprint/role), we can use the client hello as a clue that // the peer has chosen the client role, and proceed with the handshake. // The fingerprint will be verified when it's set. if (!dtls_ && local_certificate_) {
SetDtlsRole(SSL_SERVER);
SetupDtls();
}
} else {
RTC_LOG(LS_INFO) << ToString()
<< ": Not a DTLS ClientHello packet; dropping.";
} break;
case DtlsTransportState::kConnecting: case DtlsTransportState::kConnected: // We should only get DTLS or SRTP packets; STUN's already been demuxed. // Is this potentially a DTLS packet? if (IsDtlsPacket(packet.payload())) { if (!HandleDtlsPacket(packet.payload())) {
RTC_LOG(LS_ERROR) << ToString() << ": Failed to handle DTLS packet."; return;
}
} else { // Not a DTLS packet; our handshake should be complete by now. if (dtls_state() != DtlsTransportState::kConnected) {
RTC_LOG(LS_ERROR) << ToString()
<< ": Received non-DTLS packet before DTLS " "complete."; return;
}
// And it had better be a SRTP packet. if (!IsRtpPacket(packet.payload())) {
RTC_LOG(LS_ERROR)
<< ToString() << ": Received unexpected non-DTLS packet."; return;
}
// Signal this upwards as a bypass packet.
NotifyPacketReceived(
packet.CopyAndSet(ReceivedIpPacket::kSrtpEncrypted));
} break; case DtlsTransportState::kFailed: case DtlsTransportState::kClosed: case DtlsTransportState::kNumValues: // This shouldn't be happening. Drop the packet. break;
}
}
void DtlsTransportInternalImpl::OnReadyToSend(
PacketTransportInternal* /* transport */) {
RTC_DCHECK_RUN_ON(&thread_checker_); if (writable()) {
NotifyReadyToSend(this);
}
}
void DtlsTransportInternalImpl::OnDtlsEvent(int sig, int err) {
RTC_DCHECK_RUN_ON(&thread_checker_);
RTC_DCHECK(dtls_);
if (sig & SE_OPEN) { // This is the first time.
RTC_LOG(LS_INFO) << ToString() << ": DTLS handshake complete."; // The check for OPEN shouldn't be necessary but let's make // sure we don't accidentally frob the state if it's closed. if (dtls_->GetState() == SS_OPEN) { int ssl_version_bytes; bool ret = dtls_->GetSslVersionBytes(&ssl_version_bytes);
RTC_DCHECK(ret);
dtls_stun_piggyback_controller_.SetDtlsHandshakeComplete(
dtls_role_ == SSL_CLIENT, ssl_version_bytes == kDtls13VersionBytes);
set_dtls_state(DtlsTransportState::kConnected);
set_writable(true);
}
} if (sig & SE_READ) {
uint8_t buf[kMaxDtlsPacketLen];
size_t read; int read_error;
StreamResult ret; // The underlying DTLS stream may have received multiple DTLS records in // one packet, so read all of them. do {
ret = dtls_->Read(buf, read, read_error); if (ret == SR_SUCCESS) { // TODO(bugs.webrtc.org/15368): It should be possible to use information // from the original packet here to populate socket address and // timestamp.
NotifyPacketReceived(ReceivedIpPacket(
std::span(buf, read), SocketAddress(), env_.clock().CurrentTime(),
EcnMarking::kNotEct, ReceivedIpPacket::kDtlsDecrypted));
} elseif (ret == SR_EOS) { // Remote peer shut down the association with no error.
RTC_LOG(LS_INFO) << ToString() << ": DTLS transport closed by remote";
set_writable(false);
set_dtls_state(DtlsTransportState::kClosed);
NotifyOnClose();
CompleteDtlsInStun(/*success=*/false);
} elseif (ret == SR_ERROR) { // Remote peer shut down the association with an error.
RTC_LOG(LS_INFO)
<< ToString()
<< ": Closed by remote with DTLS transport error, code="
<< read_error;
set_writable(false);
set_dtls_state(DtlsTransportState::kFailed);
NotifyOnClose();
CompleteDtlsInStun(/*success=*/false);
}
} while (ret == SR_SUCCESS);
} if (sig & SE_CLOSE) {
RTC_DCHECK(sig == SE_CLOSE); // SE_CLOSE should be by itself.
set_writable(false); if (!err) {
RTC_LOG(LS_INFO) << ToString() << ": DTLS transport closed";
set_dtls_state(DtlsTransportState::kClosed);
} else {
RTC_LOG(LS_INFO) << ToString() << ": DTLS transport error, code=" << err;
set_dtls_state(DtlsTransportState::kFailed);
}
}
}
void DtlsTransportInternalImpl::MaybeStartDtls() { // When adding the DTLS handshake in STUN we want to call StartSSL even // before the ICE transport is ready. if (dtls_ && (ice_transport()->writable() || dtls_in_stun_)) {
ConfigureHandshakeTimeout();
RTC_LOG(LS_INFO)
<< ToString()
<< ": DtlsTransportInternalImpl: Start DTLS handshake active="
<< IsDtlsActive()
<< " role=" << (*dtls_role_ == SSL_SERVER ? "server" : "client"); if (dtls_->StartSSL()) { // This should never fail: // Because we are operating in a nonblocking mode and all // incoming packets come in via OnReadPacket(), which rejects // packets in this state, the incoming queue must be empty. We // ignore write errors, thus any errors must be because of // configuration and therefore are our fault.
RTC_LOG(LS_ERROR) << ToString() << ": Couldn't start DTLS handshake";
RTC_DCHECK_NOTREACHED() << "StartSSL failed.";
set_dtls_state(DtlsTransportState::kFailed); return;
}
set_dtls_state(DtlsTransportState::kConnecting); // Now that the handshake has started, we can process a cached ClientHello // (if one exists). if (!cached_client_hello_.empty()) { if (*dtls_role_ == SSL_SERVER) { int size = cached_client_hello_.size();
RTC_LOG(LS_INFO) << ToString() << ": Handling #" << size
<< " cached DTLS ClientHello packet(s)."; for (int i = 0; i < size; i++) { if (!HandleDtlsPacket(cached_client_hello_.GetNext())) {
RTC_LOG(LS_ERROR)
<< ToString() << ": Failed to handle DTLS packet."; break;
}
}
} else {
RTC_LOG(LS_WARNING) << ToString()
<< ": Discarding cached DTLS ClientHello packet " "because we don't have the server role.";
}
cached_client_hello_.clear();
}
}
}
// Called from OnReadPacket when a DTLS packet is received. bool DtlsTransportInternalImpl::HandleDtlsPacket(
std::span<const uint8_t> payload) { // Pass to the StreamInterfaceChannel which ends up being passed to the DTLS // stack. return downward_->OnPacketReceived(payload);
}
void DtlsTransportInternalImpl::set_writable(bool writable) { if (writable_ == writable) { return;
} if (writable && !ice_has_been_writable_) { // Wait with reporting writable until ICE has become writable once, // so as to not confuse other part of stack (such as sctp).
RTC_DCHECK(dtls_in_stun_);
RTC_LOG(LS_INFO)
<< ToString()
<< ": defer set_writable(true) until ICE has become writable once"; return;
}
void DtlsTransportInternalImpl::ConfigureHandshakeTimeout() {
RTC_DCHECK(dtls_);
std::optional<int> rtt_ms = ice_transport()->GetRttEstimate(); if (rtt_ms) { // Limit the timeout to a reasonable range in case the ICE RTT takes // extreme values. int initial_timeout_ms = ComputeRetransmissionTimeout(*rtt_ms);
RTC_LOG(LS_INFO) << ToString() << ": configuring DTLS handshake timeout "
<< initial_timeout_ms << "ms based on ICE RTT " << *rtt_ms;
dtls_->SetInitialRetransmissionTimeout(initial_timeout_ms);
} elseif (dtls_in_stun_) { // Configure a very high timeout to effectively disable the DTLS timeout // and avoid fragmented resends. This is ok since DTLS-in-STUN caches // the handshake pacets and resends them using the pacing of ICE.
RTC_LOG(LS_INFO) << ToString() << ": configuring DTLS handshake timeout "
<< kDisabledHandshakeTimeoutMs << "ms for DTLS-in-STUN";
dtls_->SetInitialRetransmissionTimeout(kDisabledHandshakeTimeoutMs);
} else {
RTC_LOG(LS_INFO)
<< ToString()
<< ": no RTT estimate - using default DTLS handshake timeout";
}
}
void DtlsTransportInternalImpl::UpdateHandshakeTimeout() {
RTC_DCHECK(dtls_); constauto rtt_ms = ice_transport()->GetRttEstimate(); int delay_ms = ComputeRetransmissionTimeout(
rtt_ms.value_or(kDefaultHandshakeEstimateRttMs)); if (dtls_stun_piggyback_controller_.state() ==
DtlsStunPiggybackController::State::OFF &&
dtls_role_ == SSL_CLIENT) { // We sent one STUN BINDING request with an embedded DTLS packet and // discovered that peer does not support DtlsInStun. The DTLS packet will be // sent by PeriodicRetransmitDtlsPacketUntilDtlsConnected and that will // incur one more RTT. Increase slightly timeout to avoid unneeded DTLS // retranmission.
delay_ms = (delay_ms * 133) / 100;
}
RTC_LOG(LS_INFO) << ToString() << ": Update DTLS handshake timeout to "
<< delay_ms << "ms based on ICE RTT "
<< (rtt_ms ? std::to_string(*rtt_ms) : "<unset>");
dtls_->UpdateRetransmissionTimeout(delay_ms);
}
if (pending_periodic_retransmit_dtls_packet_ == true) { // PeriodicRetransmitDtlsPacketUntilDtlsConnected is called in two places // a) Either by PostTask, where pending_ping_until_dtls_connected_ is FALSE // b) When Ice get connected, in which it is unknown if // pending_periodic_retransmit_dtls_packet_ is true or false. return;
}
if (dtls_stun_piggyback_controller_.state() ==
DtlsStunPiggybackController::State::COMPLETE) { // We're done. return;
}
if (ice_transport()->writable() && dtls_in_stun_) { auto data_to_send = dtls_stun_piggyback_controller_.GetPending(); if (data_to_send.empty()) { // No data to send, we're done. return;
} for (constauto& packet : data_to_send) {
AsyncSocketPacketOptions packet_options;
ice_transport()->SendPacket(reinterpret_cast<constchar*>(packet.data()),
packet.size(), packet_options, /* flags= */ 0);
}
}
if (dtls_stun_piggyback_controller_.state() ==
DtlsStunPiggybackController::State::OFF) { // Peer does not support DTLS in STUN. We have now retransmitted the packet // once, and let DTLS handle further retransmits. return;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.