/* * Copyright 2011 The WebRTC Project Authors. All rights reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree.
*/
// We don't pull the RTP constants from rtputils.h, to avoid a layer violation. staticconst size_t kDtlsRecordHeaderLen = 13; staticconst size_t kMaxDtlsPacketLen = 2048; staticconst 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. staticconst 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. staticconstint kMinHandshakeTimeout = 50; staticconstint kMaxHandshakeTimeout = 3000;
if (state_ == rtc::SS_CLOSED) return rtc::SR_EOS; if (state_ == rtc::SS_OPENING) return rtc::SR_BLOCK;
if (!packets_.ReadFront(buffer.data(), buffer.size(), &read)) { return rtc::SR_BLOCK;
}
return rtc::SR_SUCCESS;
}
rtc::StreamResult StreamInterfaceChannel::Write(
rtc::ArrayView<const uint8_t> data,
size_t& written, int& error) {
RTC_DCHECK_RUN_ON(&callback_sequence_); // Always succeeds, since this is an unreliable transport anyway. // TODO(zhihuang): Should this block if ice_transport_'s temporarily // unwritable?
rtc::PacketOptions packet_options;
ice_transport_->SendPacket(reinterpret_cast<constchar*>(data.data()),
data.size(), packet_options);
written = data.size(); return rtc::SR_SUCCESS;
}
bool StreamInterfaceChannel::OnPacketReceived(constchar* data, size_t size) {
RTC_DCHECK_RUN_ON(&callback_sequence_); if (packets_.size() > 0) {
RTC_LOG(LS_WARNING) << "Packet already in queue.";
} bool ret = packets_.WriteBack(data, size, NULL); 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.";
}
FireEvent(rtc::SE_READ, 0); return ret;
}
bool DtlsTransport::SetDtlsRole(rtc::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;
} returntrue;
}
webrtc::RTCError DtlsTransport::SetRemoteParameters(
absl::string_view digest_alg, const uint8_t* digest,
size_t digest_len,
std::optional<rtc::SSLRole> role) {
rtc::Buffer remote_fingerprint_value(digest, digest_len); bool is_dtls_restart =
dtls_active_ && remote_fingerprint_value_ != remote_fingerprint_value; // Set SSL role. Role must be set before fingerprint is applied, which // initiates DTLS setup. if (role) { if (is_dtls_restart) {
dtls_role_ = *role;
} else { if (!SetDtlsRole(*role)) { return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER, "Failed to set SSL role for the transport.");
}
}
} // Apply remote fingerprint. if (!SetRemoteFingerprint(digest_alg, digest, digest_len)) { return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER, "Failed to apply remote fingerprint.");
} return webrtc::RTCError::OK();
}
// 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"; returntrue;
}
// 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; returntrue;
}
// 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_.size() > 0u;
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.
rtc::SSLPeerCertificateDigestError err; if (!dtls_->SetPeerCertificateDigest(
remote_fingerprint_algorithm_, reinterpret_cast<unsignedchar*>(remote_fingerprint_value_.data()),
remote_fingerprint_value_.size(), &err)) {
RTC_LOG(LS_ERROR) << ToString()
<< ": Couldn't set DTLS certificate digest.";
set_dtls_state(webrtc::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 == rtc::SSLPeerCertificateDigestError::VERIFICATION_FAILED;
} returntrue;
}
// 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(webrtc::DtlsTransportState::kNew);
set_writable(false);
}
if (!SetupDtls()) {
set_dtls_state(webrtc::DtlsTransportState::kFailed); returnfalse;
}
returntrue;
}
std::unique_ptr<rtc::SSLCertChain> DtlsTransport::GetRemoteSSLCertChain() const { if (!dtls_) { return nullptr;
}
// 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.";
}
// Called from upper layers to send a media packet. int DtlsTransport::SendPacket(constchar* data,
size_t size, const rtc::PacketOptions& options, int flags) { if (!dtls_active_) { // Not doing DTLS. return ice_transport_->SendPacket(data, size, options);
}
switch (dtls_state()) { case webrtc::DtlsTransportState::kNew: // Can't send data until the connection is active. // TODO(ekr@rtfm.com): assert here if dtls_ is NULL? return -1; case webrtc::DtlsTransportState::kConnecting: // Can't send data until the connection is active. return -1; case webrtc::DtlsTransportState::kConnected: if (flags & PF_SRTP_BYPASS) {
RTC_DCHECK(!srtp_ciphers_.empty()); if (!IsRtpPacket(rtc::MakeArrayView( reinterpret_cast<const uint8_t*>(data), size))) { return -1;
}
return ice_transport_->SendPacket(data, size, options);
} else {
size_t written; int error; return (dtls_->WriteAll(
rtc::MakeArrayView(reinterpret_cast<const uint8_t*>(data),
size),
written, error) == rtc::SR_SUCCESS)
? static_cast<int>(size)
: -1;
} case webrtc::DtlsTransportState::kFailed: // Can't send anything when we're failed.
RTC_LOG(LS_ERROR) << ToString()
<< ": Couldn't send packet due to " "webrtc::DtlsTransportState::kFailed."; return -1; case webrtc::DtlsTransportState::kClosed: // Can't send anything when we're closed.
RTC_LOG(LS_ERROR) << ToString()
<< ": Couldn't send packet due to " "webrtc::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 DtlsTransport::OnWritableState(rtc::PacketTransportInternal* transport) {
RTC_DCHECK_RUN_ON(&thread_checker_);
RTC_DCHECK(transport == ice_transport_);
RTC_LOG(LS_VERBOSE) << ToString()
<< ": ice_transport writable state changed to "
<< ice_transport_->writable();
if (!dtls_active_) { // Not doing DTLS. // Note: SignalWritableState fired by set_writable.
set_writable(ice_transport_->writable()); return;
}
switch (dtls_state()) { case webrtc::DtlsTransportState::kNew:
MaybeStartDtls(); break; case webrtc::DtlsTransportState::kConnected: // Note: SignalWritableState fired by set_writable.
set_writable(ice_transport_->writable()); break; case webrtc::DtlsTransportState::kConnecting: // Do nothing. break; case webrtc::DtlsTransportState::kFailed: // Should not happen. Do nothing.
RTC_LOG(LS_ERROR) << ToString()
<< ": OnWritableState() called in state " "webrtc::DtlsTransportState::kFailed."; break; case webrtc::DtlsTransportState::kClosed: // Should not happen. Do nothing.
RTC_LOG(LS_ERROR) << ToString()
<< ": OnWritableState() called in state " "webrtc::DtlsTransportState::kClosed."; break; case webrtc::DtlsTransportState::kNumValues:
RTC_DCHECK_NOTREACHED(); break;
}
}
void DtlsTransport::OnReceivingState(rtc::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() == webrtc::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 webrtc::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_.SetData(packet.payload()); // 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(rtc::SSL_SERVER);
SetupDtls();
}
} else {
RTC_LOG(LS_INFO) << ToString()
<< ": Not a DTLS ClientHello packet; dropping.";
} break;
case webrtc::DtlsTransportState::kConnecting: case webrtc::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() != webrtc::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(rtc::ReceivedPacket::kSrtpEncrypted));
} break; case webrtc::DtlsTransportState::kFailed: case webrtc::DtlsTransportState::kClosed: case webrtc::DtlsTransportState::kNumValues: // This shouldn't be happening. Drop the packet. break;
}
}
void DtlsTransport::OnDtlsEvent(int sig, int err) {
RTC_DCHECK_RUN_ON(&thread_checker_); if (sig & rtc::SE_OPEN) { // This is the first time.
RTC_LOG(LS_INFO) << ToString() << ": DTLS handshake complete."; if (dtls_->GetState() == rtc::SS_OPEN) { // The check for OPEN shouldn't be necessary but let's make // sure we don't accidentally frob the state if it's closed.
set_dtls_state(webrtc::DtlsTransportState::kConnected);
set_writable(true);
}
} if (sig & rtc::SE_READ) {
uint8_t buf[kMaxDtlsPacketLen];
size_t read; int read_error;
rtc::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 == rtc::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(rtc::ReceivedPacket(
rtc::MakeArrayView(buf, read), rtc::SocketAddress(),
webrtc::Timestamp::Micros(rtc::TimeMicros()),
rtc::EcnMarking::kNotEct, rtc::ReceivedPacket::kDtlsDecrypted));
} elseif (ret == rtc::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(webrtc::DtlsTransportState::kClosed);
NotifyOnClose();
} elseif (ret == rtc::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(webrtc::DtlsTransportState::kFailed);
NotifyOnClose();
}
} while (ret == rtc::SR_SUCCESS);
} if (sig & rtc::SE_CLOSE) {
RTC_DCHECK(sig == rtc::SE_CLOSE); // SE_CLOSE should be by itself.
set_writable(false); if (!err) {
RTC_LOG(LS_INFO) << ToString() << ": DTLS transport closed";
set_dtls_state(webrtc::DtlsTransportState::kClosed);
} else {
RTC_LOG(LS_INFO) << ToString() << ": DTLS transport error, code=" << err;
set_dtls_state(webrtc::DtlsTransportState::kFailed);
}
}
}
void DtlsTransport::MaybeStartDtls() { if (dtls_ && ice_transport_->writable()) {
ConfigureHandshakeTimeout();
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_DCHECK_NOTREACHED() << "StartSSL failed.";
RTC_LOG(LS_ERROR) << ToString() << ": Couldn't start DTLS handshake";
set_dtls_state(webrtc::DtlsTransportState::kFailed); return;
}
RTC_LOG(LS_INFO) << ToString()
<< ": DtlsTransport: Started DTLS handshake active="
<< IsDtlsActive();
set_dtls_state(webrtc::DtlsTransportState::kConnecting); // Now that the handshake has started, we can process a cached ClientHello // (if one exists). if (cached_client_hello_.size()) { if (*dtls_role_ == rtc::SSL_SERVER) {
RTC_LOG(LS_INFO) << ToString()
<< ": Handling cached DTLS ClientHello packet."; if (!HandleDtlsPacket(cached_client_hello_)) {
RTC_LOG(LS_ERROR) << ToString() << ": Failed to handle DTLS packet.";
}
} 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 DtlsTransport::HandleDtlsPacket(rtc::ArrayView<const uint8_t> payload) { // Sanity check we're not passing junk that // just looks like DTLS. const uint8_t* tmp_data = payload.data();
size_t tmp_size = payload.size(); while (tmp_size > 0) { if (tmp_size < kDtlsRecordHeaderLen) returnfalse; // Too short for the header
size_t record_len = (tmp_data[11] << 8) | (tmp_data[12]); if ((record_len + kDtlsRecordHeaderLen) > tmp_size) returnfalse; // Body too short
// Looks good. Pass to the SIC which ends up being passed to // the DTLS stack. return downward_->OnPacketReceived( reinterpret_cast<constchar*>(payload.data()), payload.size());
}
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.